Compare commits

..
Author SHA1 Message Date
Sebastian Stenzel a6e680e32d Merge branch 'release/1.5.0-beta2' 2020-01-09 17:15:55 +01:00
Sebastian Stenzel d60a024c63 preparing 1.5.0-beta2 2020-01-09 16:50:38 +01:00
Sebastian StenzelandGitHub cc1b536656 New Crowdin translations (#1039)
[ci skip]
2020-01-09 16:49:39 +01:00
Sebastian Stenzel 85e773abef enable themes for donators 2020-01-09 16:34:31 +01:00
Sebastian Stenzel 4690bc56aa fixes #1038 2020-01-09 16:04:46 +01:00
Sebastian Stenzel 292c2676fd fixes #980 2020-01-09 15:44:25 +01:00
Sebastian Stenzel f7c6604c7c fixes #1029 2020-01-09 15:16:13 +01:00
Sebastian Stenzel 22310eb957 updated donation key store uri 2020-01-09 14:38:23 +01:00
Sebastian Stenzel 6d17d1298b removed unused fields 2020-01-09 13:03:22 +01:00
Sebastian Stenzel e90a680620 Split up MainWindowController, as it grew too big. The title bar is now handled by MainWindowTitleController
(@zhouer add the minimize button for #1035 to this new controller and fxml file)
2020-01-09 12:18:24 +01:00
Sebastian Stenzel 35d0ccfe89 allow resizing main window on all four corners
fixes #995
2020-01-09 12:06:12 +01:00
Tobias Hagemann 62cc454367 updated license public key 2020-01-09 10:35:27 +01:00
Tobias Hagemann 8bb277fe56 added iCloud Drive and OneDrive location presets 2020-01-09 10:27:56 +01:00
Sebastian Stenzel 67485b76af Make sure that adding a vault located at ~/foo is noop when a vault located at /home/user/foo already exists
fixes #1032
2020-01-08 16:50:08 +01:00
Sebastian Stenzel 270e60d7cf updated logging
[ci skip]
2020-01-08 15:41:26 +01:00
Sebastian Stenzel 11ffec862e Added autostart support for windows
fixes #418
2020-01-07 17:56:05 +01:00
Sebastian Stenzel d37e0dd5e1 Determine autostart state async (#418) 2020-01-07 17:55:36 +01:00
Sebastian Stenzel 960c3665e0 Localizations 2020-01-06 12:54:51 +01:00
Sebastian Stenzel 3396c79c38 updated intellij config
[ci skip]
2020-01-06 12:45:01 +01:00
Sebastian Stenzel cc1517f1b8 Added checkbox for auto start on macOS (#418) 2020-01-06 12:37:36 +01:00
Sebastian Stenzel 4f3ff55ae7 updated stale config
[ci skip]
2020-01-06 09:48:18 +01:00
Sebastian Stenzel f51362e95e cleanup
[ci skip]
2020-01-06 09:46:14 +01:00
Sebastian Stenzel 415dfece58 sames as in 9b6145ce:
We only want the user to trigger the listener
2020-01-06 09:46:01 +01:00
Sebastian StenzelandGitHub 3bdb5353da New Crowdin translations (#1015)
[ci skip]
2020-01-04 22:11:48 +01:00
Sebastian StenzelandGitHub 6ddf1b59ef Merge pull request #1031 from zhouer/hide_start_hidden
Hide startHidden setting when system tray is not supported
2019-12-29 14:18:35 +01:00
En-Jan Chou ef88f11c4f Hide startHidden setting when system tray is not supported
For platform not supporting system tray, users will have no chance to show
window and modify settings back again if the window is hidden at startup.
2019-12-26 09:40:33 -05:00
Sebastian Stenzel ec800c5439 Enforce minimum password length (amendment to #1018) 2019-12-18 15:31:52 +01:00
Sebastian Stenzel a2f3f5d254 fixes #1018, references #979 2019-12-18 14:58:36 +01:00
Sebastian Stenzel f475f70adf Updated zxcvbn4j to 1.3.0, which allows us to swipe the password from memory even after measuring its strength 2019-12-18 14:40:04 +01:00
Sebastian StenzelandGitHub 93c3da66da Merge pull request #1024 from zhouer/fix_mount_point
Initialize radio button before binding settings to it
2019-12-18 12:16:12 +01:00
En-Jan Chou 9b6145ce3f Initialize radio button before binding settings to it
The usesIndividualMountPath setting will be cleared if it bind to
an uninitialized radio button. This patch sets radio button with
current settings before binding the setting to the radio button.
2019-12-18 02:39:03 -05:00
Sebastian Stenzel 148eed172a Deduplicated UI code
Made "Enter Password" and "Reenter Password" a reusable component that is included in its original places
preparation for #1018
2019-12-17 17:35:16 +01:00
Sebastian Stenzel c97f146964 enabled "lock all" from tray menu (#297, #863) 2019-12-16 14:44:14 +01:00
Sebastian Stenzel a1034f5663 Refactored "lock all" task 2019-12-16 14:43:33 +01:00
Sebastian Stenzel 938b351f33 enabled "reveal" from tray menu (#297) 2019-12-16 13:10:43 +01:00
Sebastian Stenzel d870ecbcdb moved reveal() to VaultService 2019-12-16 13:03:28 +01:00
Sebastian Stenzel 5dff2126c4 cleanup of new VaultService 2019-12-16 11:22:30 +01:00
Sebastian Stenzel 41a6cc15e8 run "lock all" service on shared executorservice 2019-12-16 10:32:35 +01:00
Sebastian Stenzel f21b30c009 removed unused field 2019-12-16 10:31:04 +01:00
Sebastian Stenzel d69b63acc3 enabled "lock" from tray menu (#297) 2019-12-11 15:57:39 +01:00
Sebastian Stenzel f95b2baad5 "lock" deduplication 2019-12-11 15:57:14 +01:00
Sebastian Stenzel 1a4d1fffb3 shutdown logback manually 2019-12-11 13:53:32 +01:00
Sebastian Stenzel ac536ba125 Some shutdown hook refactorings for #980 2019-12-04 17:50:30 +01:00
Sebastian Stenzel 362b225d66 Fixes #939 2019-11-27 16:39:40 +01:00
Sebastian Stenzel 7d0bdc1a63 Show stack trace in case of unexpected exceptions 2019-11-27 15:44:53 +01:00
Sebastian Stenzel 94e3b21e44 Typo
[ci skip]
2019-11-27 13:20:44 +01:00
Sebastian Stenzel 23f89c1dc9 Reevaluate vault state when selecting vault 2019-11-26 17:21:28 +01:00
Sebastian Stenzel ce9c2f2c7a removed unused import
[ci skip]
2019-11-26 17:21:08 +01:00
Tobias Hagemann 44e97ab046 smaller text size for path in reveal button [ci skip] 2019-11-26 17:02:13 +01:00
Sebastian Stenzel 5d0a1fd49f tabpane styling
[ci skip]
2019-11-26 16:39:32 +01:00
Sebastian Stenzel ffc1d8dc1e simplified 2019-11-22 14:23:55 +01:00
Sebastian Stenzel 54fca93bba deduplicated code 2019-11-22 14:21:51 +01:00
Sebastian Stenzel c281687910 added copy/print buttons to recovery key dialog 2019-11-22 13:43:40 +01:00
Sebastian Stenzel 6229d7abbe Updated password and recovery key instructions 2019-11-22 13:10:50 +01:00
Sebastian Stenzel 01644eddb6 increase wizard dimensions
[ci skip]
2019-11-21 16:58:05 +01:00
Sebastian Stenzel 6e30df3796 Let the user choose whether a recovery key should be shown during vault creation 2019-11-21 16:57:43 +01:00
Sebastian Stenzel f87fa319ff renamed class 2019-11-20 17:14:31 +01:00
Sebastian Stenzel 9b019726bb show badge for missing license key in window title 2019-11-20 17:12:11 +01:00
Sebastian StenzelandGitHub cceafb76ed New Crowdin translations
New translations strings.properties
[ci skip]
2019-11-20 17:09:01 +01:00
Tobias Hagemann 39e2994c69 updated drag-n-drop-indicator styling 2019-11-20 16:53:02 +01:00
Sebastian Stenzel 1ddfcc3219 reworked drag'n'drop l&f 2019-11-20 15:03:03 +01:00
Tobias Hagemann 687f11596e added disabled styling to choice-box 2019-11-19 21:52:52 +01:00
Sebastian Stenzel b445f614fb Made mount point settings localizable
(and some fxml cleanup)
[ci skip]
2019-11-19 21:39:00 +01:00
Sebastian Stenzel c6fc1d93a0 save donation key to settings 2019-11-19 21:20:00 +01:00
Sebastian Stenzel 28d58922e3 TextArea needs a prefRowCount, otherwise it attempts to use all available space, displacing other elements.
fixes #1012
2019-11-18 10:11:20 +01:00
Sebastian Stenzel b884ea7ddc added registration key to preferences 2019-11-15 15:56:35 +01:00
Sebastian Stenzel 1717e20b61 Add JWT verifier 2019-11-14 17:02:41 +01:00
Sebastian Stenzel 6222f96b01 Merge branch 'master' into develop 2019-11-12 13:40:34 +01:00
Sebastian Stenzel 88f83c5cdb Merge branch 'release/1.5.0-beta1' 2019-11-12 13:33:08 +01:00
Sebastian Stenzel c8059bef78 bumped version
[ci skip]
2019-11-12 13:32:51 +01:00
Sebastian Stenzel 638b731cf8 always set version to SNAPSHOT-{commit} for untagged CI builds 2019-11-12 13:23:05 +01:00
Sebastian Stenzel b2b6e304ed updated CI config 2019-11-12 13:13:58 +01:00
Sebastian Stenzel 0748709f40 Updated dependencies 2019-11-11 16:42:56 +01:00
Sebastian StenzelandGitHub d8ee446d37 New Crowdin translations (#987)
* New translations strings.properties
[ci skip]
2019-11-11 16:36:48 +01:00
Sebastian Stenzel 9932185ccc fixed typo
kudos to our translation team
[ci skip]
2019-10-31 22:10:37 +01:00
Sebastian Stenzel 77afd4688a ide settings
[ci skip]
2019-10-31 22:10:09 +01:00
Sebastian Stenzel 9a5ef3f6ff updated cryptofs to 1.9.0-rc1 2019-10-31 01:29:30 +01:00
Sebastian Stenzel c99f23a4c7 Merge branch 'support/1.4.x' into develop
# Conflicts:
#	main/buildkit/pom.xml
#	main/commons/pom.xml
#	main/keychain/pom.xml
#	main/launcher/pom.xml
#	main/pom.xml
#	main/ui/pom.xml
2019-10-31 01:11:02 +01:00
Sebastian Stenzel 274c3886f5 Merge branch 'hotfix/1.4.17' into support/1.4.x 2019-10-31 01:01:24 +01:00
Sebastian Stenzel 95db29158c fixes #997 2019-10-31 00:54:49 +01:00
Sebastian Stenzel 872680a737 updated dependencies, removed commons-io 2019-10-31 00:54:37 +01:00
Sebastian Stenzel 0ba12f6301 bumped version to 1.4.17 2019-10-31 00:54:09 +01:00
Armin Schrenk 248df9da51 TODOs 2019-10-23 14:23:53 +02:00
Armin Schrenk 702408d488 phrasing 2019-10-23 14:23:44 +02:00
Armin Schrenk dd1506f17a windows: fixing that already used drive letters are not shown as such in drop down menu 2019-10-23 13:07:41 +02:00
Sebastian Stenzel 7fb5c741ad get rid of some legacy code with too much if/else 2019-10-18 14:34:48 +02:00
Sebastian Stenzel aa61ab2b6e allow changing of user interface orientation via settings 2019-10-18 12:51:41 +02:00
Armin Schrenk 7a1e20d732 showing custom mountPath if selected 2019-10-16 16:58:50 +02:00
Armin Schrenk c1a5e187b6 localization 2019-10-16 16:49:21 +02:00
Armin Schrenk 57d3f788e6 further improving windows mount options 2019-10-16 16:41:28 +02:00
Armin Schrenk ca73c3ad90 Updating WinDriveLetters since Dokany be installed in version 1.3.x 2019-10-16 15:57:08 +02:00
Armin Schrenk aec367dcc7 adding TODO 2019-10-16 15:49:17 +02:00
Armin Schrenk 56e7c13cb1 including windows mount option for selecting mount point (without functionality) 2019-10-16 14:32:10 +02:00
Sebastian Stenzel 3b4f384bfd there shouldn't be a "."
[ci skip]
2019-10-14 10:51:16 +02:00
Sebastian Stenzel d1a20da7e0 Localization [ci skip] 2019-10-14 10:22:23 +02:00
Tobias HagemannandGitHub 4b3533f717 say my name [ci skip] 2019-10-14 00:31:49 +02:00
Sebastian Stenzel 6535adef44 Merge branch 'master' into develop 2019-10-13 15:42:09 +02:00
122 changed files with 3920 additions and 1052 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [overheadhunter] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: [overheadhunter, tobihagemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
+3 -1
View File
@@ -1,12 +1,14 @@
# Number of days of inactivity before an issue becomes stale # Number of days of inactivity before an issue becomes stale
daysUntilStale: 90 daysUntilStale: 180
# Number of days of inactivity before a stale issue is closed # Number of days of inactivity before a stale issue is closed
daysUntilClose: 30 daysUntilClose: 30
# Issues with these labels will never be considered stale # Issues with these labels will never be considered stale
exemptLabels: exemptLabels:
- type:security-issue # never close automatically - type:security-issue # never close automatically
- type:feature-request # never close automatically
- state:awaiting-response # handled by different bot - state:awaiting-response # handled by different bot
- state:blocked - state:blocked
- state:confirmed
# Set to true to ignore issues in a milestone (defaults to false) # Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true exemptMilestones: true
# Label to use when marking an issue as stale # Label to use when marking an issue as stale
+17 -9
View File
@@ -7,21 +7,29 @@
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" /> <outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false"> <processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.22.1/dagger-compiler-2.22.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.25.2/dagger-compiler-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.22.1/dagger-2.22.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.25.2/dagger-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" /> <entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.22.1/dagger-producers-2.22.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.25.2/dagger-producers-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/25.0-jre/guava-25.0-jre.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar" /> <entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.22.1/dagger-spi-2.22.1.jar" /> <entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.25.2/dagger-spi-2.25.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" /> <entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
</processorPath> </processorPath>
<module name="keychain" /> <module name="keychain" />
<module name="launcher" /> <module name="launcher" />
+8 -6
View File
@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM"> <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/buildkit" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main/launcher/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/ui" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/main/ui/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/main/ui/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component> </component>
</project> </project>
+11 -10
View File
@@ -1,4 +1,4 @@
dist: xenial dist: bionic
language: java language: java
sudo: false sudo: false
jdk: jdk:
@@ -17,24 +17,25 @@ addons:
- haveged - haveged
install: install:
- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml - curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml
- mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 - mvn -fmain/pom.xml clean install -DskipTests -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568
before_script:
- |
if [[ -n "$TRAVIS_TAG" ]]; then
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
else
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
fi
script: script:
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage - mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
after_success: after_success:
- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.2/codacy-coverage-reporter-4.0.2-assembly.jar - curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/6.0.7/codacy-coverage-reporter-6.0.7-assembly.jar
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
before_deploy: before_deploy:
- | - mvn -fmain/pom.xml package -Prelease -DskipTests
if [[ -n "$TRAVIS_TAG" ]]; then
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
fi
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json - export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
deploy: deploy:
- provider: bintray # SNAPSHOTS - provider: bintray # SNAPSHOTS
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
</parent> </parent>
<artifactId>buildkit</artifactId> <artifactId>buildkit</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
+7 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
</parent> </parent>
<artifactId>commons</artifactId> <artifactId>commons</artifactId>
<name>Cryptomator Commons</name> <name>Cryptomator Commons</name>
@@ -48,6 +48,12 @@
<artifactId>easybind</artifactId> <artifactId>easybind</artifactId>
</dependency> </dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<!-- Google --> <!-- Google -->
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
@@ -28,13 +28,23 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@Module(subcomponents = {VaultComponent.class}) @Module(subcomponents = {VaultComponent.class})
public abstract class CommonsModule { public abstract class CommonsModule {
private static final int NUM_SCHEDULER_THREADS = 4; private static final int NUM_SCHEDULER_THREADS = 4;
@Provides
@Singleton
@Named("licensePublicKey")
static String provideLicensePublicKey() {
// in PEM format without the dash-escaped begin/end lines
return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
+ "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
+ "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
+ "r0DzRyj4ixPIt38CQB8=";
}
@Provides @Provides
@Singleton @Singleton
@Named("SemVer") @Named("SemVer")
@@ -56,7 +66,7 @@ public abstract class CommonsModule {
@Provides @Provides
@Singleton @Singleton
static ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) { static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
final AtomicInteger threadNumber = new AtomicInteger(1); final AtomicInteger threadNumber = new AtomicInteger(1);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
Thread t = new Thread(r); Thread t = new Thread(r);
@@ -64,7 +74,7 @@ public abstract class CommonsModule {
t.setDaemon(true); t.setDaemon(true);
return t; return t;
}); });
shutdownTaskScheduler.accept(executorService::shutdown); shutdownHook.runOnShutdown(executorService::shutdown);
return executorService; return executorService;
} }
@@ -25,6 +25,7 @@ public class Environment {
private static final Path RELATIVE_HOME_DIR = Paths.get("~"); private static final Path RELATIVE_HOME_DIR = Paths.get("~");
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME); private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
private static final char PATH_LIST_SEP = ':'; private static final char PATH_LIST_SEP = ':';
private static final int DEFAULT_MIN_PW_LENGTH = 8;
@Inject @Inject
public Environment() { public Environment() {
@@ -37,6 +38,7 @@ public class Environment {
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath")); LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir")); LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir")); LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
} }
public boolean useCustomLogbackConfig() { public boolean useCustomLogbackConfig() {
@@ -63,6 +65,19 @@ public class Environment {
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir); return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
} }
public int getMinPwLength() {
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
}
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) { // includes "null" values
return defaultValue;
}
}
private Optional<Path> getPath(String propertyName) { private Optional<Path> getPath(String propertyName) {
String value = System.getProperty(propertyName); String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get); return Optional.ofNullable(value).map(Paths::get);
@@ -0,0 +1,56 @@
package org.cryptomator.common;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.google.common.io.BaseEncoding;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Optional;
@Singleton
class LicenseChecker {
private final JWTVerifier verifier;
@Inject
public LicenseChecker(@Named("licensePublicKey") String pemEncodedPublicKey) {
Algorithm algorithm = Algorithm.ECDSA512(decodePublicKey(pemEncodedPublicKey), null);
this.verifier = JWT.require(algorithm).build();
}
private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) {
try {
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
if (key instanceof ECPublicKey) {
return (ECPublicKey) key;
} else {
throw new IllegalStateException("Key not an EC public key.");
}
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException("Invalid license public key", e);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
public Optional<DecodedJWT> check(String licenseKey) {
try {
return Optional.of(verifier.verify(licenseKey));
} catch (JWTVerificationException exception) {
return Optional.empty();
}
}
}
@@ -0,0 +1,79 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.cryptomator.common.settings.Settings;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
public class LicenseHolder {
private final Settings settings;
private final LicenseChecker licenseChecker;
private final ObjectProperty<DecodedJWT> validJwtClaims;
private final StringBinding licenseSubject;
private final BooleanBinding validLicenseProperty;
@Inject
public LicenseHolder(LicenseChecker licenseChecker, Settings settings) {
this.settings = settings;
this.licenseChecker = licenseChecker;
this.validJwtClaims = new SimpleObjectProperty<>();
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
this.validLicenseProperty = validJwtClaims.isNotNull();
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
validJwtClaims.set(claims.orElse(null));
}
public boolean validateAndStoreLicense(String licenseKey) {
Optional<DecodedJWT> claims = licenseChecker.check(licenseKey);
validJwtClaims.set(claims.orElse(null));
if (claims.isPresent()) {
settings.licenseKey().set(licenseKey);
return true;
} else {
return false;
}
}
/* Observable Properties */
public Optional<String> getLicenseKey() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return Optional.of(claims.getToken());
} else {
return Optional.empty();
}
}
public StringBinding licenseSubjectProperty() {
return licenseSubject;
}
public String getLicenseSubject() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return claims.getSubject();
} else {
return null;
}
}
public BooleanBinding validLicenseProperty() {
return validLicenseProperty;
}
public boolean isValidLicense() {
return validLicenseProperty.get();
}
}
@@ -1,28 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common;
import java.util.Optional;
import java.util.function.Function;
public final class Optionals {
private Optionals() {
}
/**
* Returns a function that is equivalent to the input function but immediately gets the value of the returned optional when invoked.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @param function An {@code Optional}-bearing input function {@code Function<Foo, Optional<Bar>>}
* @return A {@code Function<Foo, Bar>}, that may throw a NoSuchElementException, if the original function returns an empty optional.
*/
public static <T, R> Function<T, R> unwrap(Function<T, Optional<R>> function) {
return t -> function.apply(t).get();
}
}
@@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common;
import com.google.common.util.concurrent.Runnables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Queue;
import java.util.concurrent.PriorityBlockingQueue;
@Singleton
public class ShutdownHook extends Thread {
private static final int PRIO_VERY_LAST = Integer.MIN_VALUE;
public static final int PRIO_LAST = PRIO_VERY_LAST + 1;
public static final int PRIO_DEFAULT = 0;
public static final int PRIO_FIRST = Integer.MAX_VALUE;
private static final Logger LOG = LoggerFactory.getLogger(ShutdownHook.class);
private static final OrderedTask POISON = new OrderedTask(PRIO_VERY_LAST, Runnables.doNothing());
private final Queue<OrderedTask> tasks = new PriorityBlockingQueue<>();
@Inject
ShutdownHook() {
super(null, null, "ShutdownTasks", 0);
Runtime.getRuntime().addShutdownHook(this);
LOG.debug("Registered shutdown hook.");
}
@Override
public void run() {
LOG.debug("Running graceful shutdown tasks...");
tasks.add(POISON);
Runnable task;
while ((task = tasks.remove()) != POISON) {
try {
task.run();
} catch (RuntimeException e) {
LOG.error("Exception while shutting down.", e);
}
}
}
/**
* Schedules a task to be run during shutdown with default order
*
* @param task The task to be scheduled
*/
public void runOnShutdown(Runnable task) {
runOnShutdown(PRIO_DEFAULT, task);
}
/**
* Schedules a task to be run with the given priority
*
* @param priority Tasks with high priority will be run before task with lower priority
* @param task The task to be scheduled
*/
public void runOnShutdown(int priority, Runnable task) {
tasks.add(new OrderedTask(priority, task));
}
private static class OrderedTask implements Comparable<OrderedTask>, Runnable {
private final int priority;
private final Runnable task;
public OrderedTask(int priority, Runnable task) {
this.priority = priority;
this.task = task;
}
@Override
public int compareTo(OrderedTask other) {
// overflow-safe signum impl:
if (this.priority > other.priority) {
return -1; // higher prio -> this before other
} else if (this.priority < other.priority) {
return +1; // lower prio -> other before this
} else {
return 0; // same prio
}
}
@Override
public void run() {
task.run();
}
}
}
@@ -15,8 +15,11 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.geometry.NodeOrientation;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -30,9 +33,11 @@ public class Settings {
public static final int DEFAULT_PORT = 42427; public static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV; public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
public static final boolean DEFAULT_DEBUG_MODE = false; public static final boolean DEFAULT_DEBUG_MODE = false;
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE; public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
private static final String DEFAULT_LICENSE_KEY = "";
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables); private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK); private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
@@ -44,6 +49,8 @@ public class Settings {
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL); private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME); private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
private Consumer<Settings> saveCmd; private Consumer<Settings> saveCmd;
@@ -61,6 +68,8 @@ public class Settings {
debugMode.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged);
preferredVolumeImpl.addListener(this::somethingChanged); preferredVolumeImpl.addListener(this::somethingChanged);
theme.addListener(this::somethingChanged); theme.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
licenseKey.addListener(this::somethingChanged);
} }
void setSaveCmd(Consumer<Settings> saveCmd) { void setSaveCmd(Consumer<Settings> saveCmd) {
@@ -118,4 +127,12 @@ public class Settings {
public ObjectProperty<UiTheme> theme() { public ObjectProperty<UiTheme> theme() {
return theme; return theme;
} }
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
return userInterfaceOrientation;
}
public StringProperty licenseKey() {
return licenseKey;
}
} }
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import javafx.geometry.NodeOrientation;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -36,6 +37,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
out.name("debugMode").value(value.debugMode().get()); out.name("debugMode").value(value.debugMode().get());
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name()); out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
out.name("theme").value(value.theme().get().name()); out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
out.name("licenseKey").value(value.licenseKey().get());
out.endObject(); out.endObject();
} }
@@ -85,6 +88,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "theme": case "theme":
settings.theme().set(parseUiTheme(in.nextString())); settings.theme().set(parseUiTheme(in.nextString()));
break; break;
case "uiOrientation":
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
break;
case "licenseKey":
settings.licenseKey().set(in.nextString());
break;
default: default:
LOG.warn("Unsupported vault setting found in JSON: " + name); LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue(); in.skipValue();
@@ -109,7 +118,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
try { try {
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase()); return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOG.warn("Invalid volume type {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME); LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME);
return Settings.DEFAULT_GVFS_SCHEME; return Settings.DEFAULT_GVFS_SCHEME;
} }
} }
@@ -118,11 +127,20 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
try { try {
return UiTheme.valueOf(uiThemeName.toUpperCase()); return UiTheme.valueOf(uiThemeName.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOG.warn("Invalid volume type {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME); LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME);
return Settings.DEFAULT_THEME; return Settings.DEFAULT_THEME;
} }
} }
private NodeOrientation parseUiOrientation(String uiOrientationName) {
try {
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION);
return Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
}
}
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException { private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
List<VaultSettings> result = new ArrayList<>(); List<VaultSettings> result = new ArrayList<>();
in.beginArray(); in.beginArray();
@@ -69,8 +69,9 @@ public class DokanyVolume implements Volume {
} else { } else {
//auto assign drive letter //auto assign drive letter
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) { if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
return windowsDriveLetters.getAvailableDriveLetters().iterator().next(); return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
} else { } else {
//TODO: Error Handling
throw new VolumeException("No free drive letter available."); throw new VolumeException("No free drive letter available.");
} }
} }
@@ -18,6 +18,7 @@ import org.cryptomator.cryptofs.migration.Migrators;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
@@ -67,7 +68,13 @@ public class VaultListManager {
} }
private Optional<Vault> get(Path vaultPath) { private Optional<Vault> get(Path vaultPath) {
return vaultList.stream().filter(v -> v.getPath().equals(vaultPath)).findAny(); return vaultList.stream().filter(v -> {
try {
return Files.isSameFile(vaultPath, v.getPath());
} catch (IOException e) {
return false;
}
}).findAny();
} }
private Vault create(VaultSettings vaultSettings) { private Vault create(VaultSettings vaultSettings) {
@@ -76,7 +83,7 @@ public class VaultListManager {
return comp.vault(); return comp.vault();
} }
private VaultState determineVaultState(Path pathToVault) { public static VaultState determineVaultState(Path pathToVault) {
try { try {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) { if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.MISSING; return VaultState.MISSING;
@@ -5,6 +5,7 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.common.vaults; package org.cryptomator.common.vaults;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -14,7 +15,6 @@ import javax.inject.Singleton;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@@ -23,11 +23,11 @@ import java.util.stream.StreamSupport;
public final class WindowsDriveLetters { public final class WindowsDriveLetters {
private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class); private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class);
private static final Set<Path> D_TO_Z; private static final Set<String> A_TO_Z;
static { static {
try (IntStream stream = IntStream.rangeClosed('D', 'Z')) { try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
D_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet()); A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
} }
} }
@@ -35,20 +35,22 @@ public final class WindowsDriveLetters {
public WindowsDriveLetters() { public WindowsDriveLetters() {
} }
public Set<Path> getOccupiedDriveLetters() { public Set<String> getAllDriveLetters() {
return A_TO_Z;
}
public Set<String> getOccupiedDriveLetters() {
if (!SystemUtils.IS_OS_WINDOWS) { if (!SystemUtils.IS_OS_WINDOWS) {
LOG.warn("Attempted to get occupied drive letters on non-Windows machine."); LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
return Set.of(); return Set.of();
} else { } else {
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories(); Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toSet()); return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0,1)).collect(Collectors.toSet());
} }
} }
public Set<Path> getAvailableDriveLetters() { public Set<String> getAvailableDriveLetters() {
Set<Path> occupiedDriveLetters = getOccupiedDriveLetters(); return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
Predicate<Path> isOccupiedDriveLetter = occupiedDriveLetters::contains;
return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
} }
} }
@@ -0,0 +1,62 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.cryptomator.common.LicenseChecker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
class LicenseCheckerTest {
private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
+ "Al8G7CqwoJOsW7Kddns=";
private LicenseChecker licenseChecker;
@BeforeEach
public void setup() {
licenseChecker = new LicenseChecker(PUBLIC_KEY);
}
@Test
public void testCheckValidLicense() {
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertTrue(decoded.isPresent());
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
}
@Test
public void testCheckInvalidLicenseHeader() {
String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertFalse(decoded.isPresent());
}
@Test
public void testCheckInvalidLicensePayload() {
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertFalse(decoded.isPresent());
}
@Test
public void testCheckInvalidLicenseSignature() {
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertFalse(decoded.isPresent());
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
</parent> </parent>
<artifactId>keychain</artifactId> <artifactId>keychain</artifactId>
<name>System Keychain Access</name> <name>System Keychain Access</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
</parent> </parent>
<artifactId>launcher</artifactId> <artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name> <name>Cryptomator Launcher</name>
@@ -1,48 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.launcher;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
class CleanShutdownPerformer extends Thread {
private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
private final ConcurrentMap<Runnable, Boolean> tasks = new ConcurrentHashMap<>();
@Inject
CleanShutdownPerformer() {
super(null, null, "ShutdownTasks", 0);
}
@Override
public void run() {
LOG.debug("Running graceful shutdown tasks...");
tasks.keySet().forEach(r -> {
try {
r.run();
} catch (RuntimeException e) {
LOG.error("Exception while shutting down.", e);
}
});
tasks.clear();
}
void scheduleShutdownTask(Runnable task) {
tasks.put(task, Boolean.TRUE);
}
void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(this);
}
}
@@ -32,17 +32,15 @@ public class Cryptomator {
private final IpcFactory ipcFactory; private final IpcFactory ipcFactory;
private final Optional<String> applicationVersion; private final Optional<String> applicationVersion;
private final CountDownLatch shutdownLatch; private final CountDownLatch shutdownLatch;
private final CleanShutdownPerformer shutdownPerformer;
private final UiLauncher uiLauncher; private final UiLauncher uiLauncher;
@Inject @Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, CleanShutdownPerformer shutdownPerformer, UiLauncher uiLauncher) { Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, UiLauncher uiLauncher) {
this.logConfig = logConfig; this.logConfig = logConfig;
this.debugMode = debugMode; this.debugMode = debugMode;
this.ipcFactory = ipcFactory; this.ipcFactory = ipcFactory;
this.applicationVersion = applicationVersion; this.applicationVersion = applicationVersion;
this.shutdownLatch = shutdownLatch; this.shutdownLatch = shutdownLatch;
this.shutdownPerformer = shutdownPerformer;
this.uiLauncher = uiLauncher; this.uiLauncher = uiLauncher;
} }
@@ -90,7 +88,6 @@ public class Cryptomator {
*/ */
private int runGuiApplication() { private int runGuiApplication() {
try { try {
shutdownPerformer.registerShutdownHook();
uiLauncher.launch(); uiLauncher.launch();
shutdownLatch.await(); shutdownLatch.await();
LOG.info("UI shut down"); LOG.info("UI shut down");
@@ -7,18 +7,10 @@ import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
@Module @Module
class CryptomatorModule { class CryptomatorModule {
@Provides
@Singleton
@Named("shutdownTaskScheduler")
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
return shutdownPerformer::scheduleShutdownTask;
}
@Provides @Provides
@Singleton @Singleton
@Named("shutdownLatch") @Named("shutdownLatch")
@@ -5,9 +5,8 @@ import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender; import ch.qos.logback.core.Appender;
import ch.qos.logback.core.hook.DelayingShutdownHook;
import ch.qos.logback.core.util.Duration;
import org.cryptomator.common.Environment; import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -17,25 +16,26 @@ import java.util.Map;
@Singleton @Singleton
public class LoggerConfiguration { public class LoggerConfiguration {
private static final double SHUTDOWN_DELAY_MS = 100;
private final LoggerContext context; private final LoggerContext context;
private final Environment environment; private final Environment environment;
private final Appender<ILoggingEvent> stdout; private final Appender<ILoggingEvent> stdout;
private final Appender<ILoggingEvent> upgrade; private final Appender<ILoggingEvent> upgrade;
private final Appender<ILoggingEvent> file; private final Appender<ILoggingEvent> file;
private final ShutdownHook shutdownHook;
@Inject @Inject
LoggerConfiguration(LoggerContext context, // LoggerConfiguration(LoggerContext context, //
Environment environment, // Environment environment, //
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, // @Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, // @Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
@Named("fileAppender") Appender<ILoggingEvent> file) { @Named("fileAppender") Appender<ILoggingEvent> file, //
ShutdownHook shutdownHook) {
this.context = context; this.context = context;
this.environment = environment; this.environment = environment;
this.stdout = stdout; this.stdout = stdout;
this.upgrade = upgrade; this.upgrade = upgrade;
this.file = file; this.file = file;
this.shutdownHook = shutdownHook;
} }
public void init() { public void init() {
@@ -62,9 +62,7 @@ public class LoggerConfiguration {
upgrades.setAdditive(false); upgrades.setAdditive(false);
// add shutdown hook // add shutdown hook
DelayingShutdownHook shutdownHook = new DelayingShutdownHook(); shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
shutdownHook.setContext(context);
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
} }
} }
+29 -29
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Cryptomator</name> <name>Cryptomator</name>
@@ -23,30 +23,28 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- dependency versions --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.0-beta4</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.2.1</cryptomator.jni.version> <cryptomator.jni.version>2.2.2</cryptomator.jni.version>
<cryptomator.fuse.version>1.2.0</cryptomator.fuse.version> <cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version> <cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version> <cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
<javafx.version>13</javafx.version> <!-- 3rd party dependencies -->
<javafx.version>13.0.1</javafx.version>
<commons-io.version>2.6</commons-io.version> <commons-lang3.version>3.9</commons-lang3.version>
<commons-lang3.version>3.8.1</commons-lang3.version> <jwt.version>3.8.3</jwt.version>
<easybind.version>1.0.3</easybind.version> <easybind.version>1.0.3</easybind.version>
<guava.version>28.1-jre</guava.version>
<guava.version>27.1-jre</guava.version> <dagger.version>2.25.2</dagger.version>
<dagger.version>2.22.1</dagger.version> <gson.version>2.8.6</gson.version>
<gson.version>2.8.5</gson.version> <slf4j.version>1.7.29</slf4j.version>
<slf4j.version>1.7.26</slf4j.version>
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
<junit.jupiter.version>5.4.2</junit.jupiter.version> <!-- test dependencies -->
<mockito.version>2.27.0</mockito.version> <junit.jupiter.version>5.5.2</junit.jupiter.version>
<hamcrest.version>2.1</hamcrest.version> <mockito.version>3.1.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
</properties> </properties>
<repositories> <repositories>
@@ -152,17 +150,19 @@
</dependency> </dependency>
<!-- Apache Commons --> <!-- Apache Commons -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version> <version>${commons-lang3.version}</version>
</dependency> </dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- EasyBind --> <!-- EasyBind -->
<dependency> <dependency>
<groupId>org.fxmisc.easybind</groupId> <groupId>org.fxmisc.easybind</groupId>
@@ -267,7 +267,7 @@
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>3.1.0</version> <version>3.1.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-libs</id> <id>copy-libs</id>
@@ -296,7 +296,7 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version> <version>0.8.5</version>
<executions> <executions>
<execution> <execution>
<id>prepare-agent</id> <id>prepare-agent</id>
@@ -323,7 +323,7 @@
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version> <version>3.8.1</version>
<configuration> <configuration>
<release>11</release> <release>11</release>
<annotationProcessorPaths> <annotationProcessorPaths>
@@ -338,7 +338,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version> <version>2.22.2</version>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
+2 -6
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.5.0-alpha2</version> <version>1.5.0-beta2</version>
</parent> </parent>
<artifactId>ui</artifactId> <artifactId>ui</artifactId>
<name>Cryptomator GUI</name> <name>Cryptomator GUI</name>
@@ -50,10 +50,6 @@
</dependency> </dependency>
<!-- Apache Commons --> <!-- Apache Commons -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
@@ -69,7 +65,7 @@
<dependency> <dependency>
<groupId>com.nulab-inc</groupId> <groupId>com.nulab-inc</groupId>
<artifactId>zxcvbn</artifactId> <artifactId>zxcvbn</artifactId>
<version>1.2.7</version> <version>1.3.0</version>
</dependency> </dependency>
<!-- Logging --> <!-- Logging -->
@@ -15,14 +15,14 @@ import javax.inject.Inject;
import java.nio.file.Path; import java.nio.file.Path;
@AddVaultWizardScoped @AddVaultWizardScoped
public class AddVaultFailureExisitingController implements FxController { public class AddVaultFailureExistingController implements FxController {
private final Stage window; private final Stage window;
private final Lazy<Scene> previousScene; private final Lazy<Scene> previousScene;
private final StringBinding vaultName; private final StringBinding vaultName;
@Inject @Inject
AddVaultFailureExisitingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){ AddVaultFailureExistingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){
this.window = window; this.window = window;
this.previousScene = previousScene; this.previousScene = previousScene;
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault); this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
@@ -19,7 +19,10 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.mainwindow.MainWindow; import org.cryptomator.ui.mainwindow.MainWindow;
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
@@ -31,6 +34,13 @@ import java.util.ResourceBundle;
@Module @Module
public abstract class AddVaultModule { public abstract class AddVaultModule {
@Provides
@AddVaultWizardScoped
@Named("newPassword")
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
return new SimpleObjectProperty<>("");
}
@Provides @Provides
@AddVaultWizardWindow @AddVaultWizardWindow
@AddVaultWizardScoped @AddVaultWizardScoped
@@ -150,8 +160,8 @@ public abstract class AddVaultModule {
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(AddVaultFailureExisitingController.class) @FxControllerKey(AddVaultFailureExistingController.class)
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller); abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExistingController controller);
@Binds @Binds
@IntoMap @IntoMap
@@ -168,13 +178,27 @@ public abstract class AddVaultModule {
@FxControllerKey(CreateNewVaultPasswordController.class) @FxControllerKey(CreateNewVaultPasswordController.class)
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller); abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
@Binds @Provides
@IntoMap @IntoMap
@FxControllerKey(AddVaultSuccessController.class) @FxControllerKey(NewPasswordController.class)
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller); static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
return new NewPasswordController(resourceBundle, strengthRater, password);
}
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(CreateNewVaultRecoveryKeyController.class) @FxControllerKey(CreateNewVaultRecoveryKeyController.class)
abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller); abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller);
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyDisplayController.class)
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey) {
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get());
}
@Binds
@IntoMap
@FxControllerKey(AddVaultSuccessController.class)
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
} }
@@ -52,8 +52,10 @@ public class CreateNewVaultLocationController implements FxController {
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH; private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
public ToggleGroup predefinedLocationToggler; public ToggleGroup predefinedLocationToggler;
public RadioButton iclouddriveRadioButton;
public RadioButton dropboxRadioButton; public RadioButton dropboxRadioButton;
public RadioButton gdriveRadioButton; public RadioButton gdriveRadioButton;
public RadioButton onedriveRadioButton;
public RadioButton customRadioButton; public RadioButton customRadioButton;
@Inject @Inject
@@ -91,10 +93,14 @@ public class CreateNewVaultLocationController implements FxController {
} }
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
if (dropboxRadioButton.equals(newValue)) { if (iclouddriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getIclouddriveLocation().resolve(vaultName.get()));
} else if (dropboxRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get())); vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
} else if (gdriveRadioButton.equals(newValue)) { } else if (gdriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get())); vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
} else if (onedriveRadioButton.equals(newValue)) {
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
} else if (customRadioButton.equals(newValue)) { } else if (customRadioButton.equals(newValue)) {
vaultPath.set(customVaultPath.resolve(vaultName.get())); vaultPath.set(customVaultPath.resolve(vaultName.get()));
} }
@@ -5,16 +5,14 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label; import javafx.scene.control.Toggle;
import javafx.scene.layout.HBox; import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
@@ -24,11 +22,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -58,6 +52,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final Stage window; private final Stage window;
private final Lazy<Scene> chooseLocationScene; private final Lazy<Scene> chooseLocationScene;
private final Lazy<Scene> recoveryKeyScene; private final Lazy<Scene> recoveryKeyScene;
private final Lazy<Scene> successScene;
private final ExecutorService executor; private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory; private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty vaultNameProperty; private final StringProperty vaultNameProperty;
@@ -66,26 +61,22 @@ public class CreateNewVaultPasswordController implements FxController {
private final StringProperty recoveryKeyProperty; private final StringProperty recoveryKeyProperty;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final PasswordStrengthUtil strengthRater; private final ObjectProperty<CharSequence> password;
private final ReadmeGenerator readmeGenerator; private final ReadmeGenerator readmeGenerator;
private final IntegerProperty passwordStrength;
private final BooleanProperty processing; private final BooleanProperty processing;
private final BooleanProperty readyToCreateVault; private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState; private final ObjectBinding<ContentDisplay> createVaultButtonState;
public NiceSecurePasswordField passwordField; public ToggleGroup recoveryKeyChoice;
public NiceSecurePasswordField reenterField; public Toggle showRecoveryKey;
public Label passwordStrengthLabel; public Toggle skipRecoveryKey;
public HBox passwordMatchBox;
public FontAwesome5IconView checkmark;
public FontAwesome5IconView cross;
public Label passwordMatchLabel;
@Inject @Inject
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) { CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
this.window = window; this.window = window;
this.chooseLocationScene = chooseLocationScene; this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene; this.recoveryKeyScene = recoveryKeyScene;
this.successScene = successScene;
this.executor = executor; this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory; this.recoveryKeyFactory = recoveryKeyFactory;
this.vaultNameProperty = vaultName; this.vaultNameProperty = vaultName;
@@ -94,9 +85,8 @@ public class CreateNewVaultPasswordController implements FxController {
this.recoveryKeyProperty = recoveryKey; this.recoveryKeyProperty = recoveryKey;
this.vaultListManager = vaultListManager; this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.strengthRater = strengthRater; this.password = password;
this.readmeGenerator = readmeGenerator; this.readmeGenerator = readmeGenerator;
this.passwordStrength = new SimpleIntegerProperty(-1);
this.processing = new SimpleBooleanProperty(); this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty(); this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing); this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
@@ -104,22 +94,8 @@ public class CreateNewVaultPasswordController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
//binds the actual strength value to the rating of the password util BooleanBinding isValidNewPassword = Bindings.createBooleanBinding(() -> password.get() != null && password.get().length() > 0, password);
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty())); readyToCreateVault.bind(isValidNewPassword.and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
//binding indicating if the passwords not match
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0, passwordField.textProperty(), reenterField.textProperty());
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(processing.not()));
//make match indicator invisible when passwords do not match or one is empty
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
checkmark.managedProperty().bind(checkmark.visibleProperty());
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
cross.managedProperty().bind(cross.visibleProperty());
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("addvaultwizard.new.passwordsMatch")).otherwise(resourceBundle.getString("addvaultwizard.new.passwordsDoNotMatch")));
//bindsings for the password strength indicator
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
} }
@FXML @FXML
@@ -141,12 +117,41 @@ public class CreateNewVaultPasswordController implements FxController {
LOG.error("", e); LOG.error("", e);
} }
if (showRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
showRecoveryKeyScene();
} else if (skipRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
showSuccessScene();
} else {
throw new IllegalStateException("Unexpected toggle state");
}
}
private void showRecoveryKeyScene() {
Path pathToVault = vaultPathProperty.get();
processing.set(true); processing.set(true);
Tasks.create(() -> { Tasks.create(() -> {
initializeVault(pathToVault, passwordField.getCharacters()); initializeVault(pathToVault, password.get());
return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters()); return recoveryKeyFactory.createRecoveryKey(pathToVault, password.get());
}).onSuccess(recoveryKey -> { }).onSuccess(recoveryKey -> {
initializationSucceeded(pathToVault, recoveryKey); initializationSucceeded(pathToVault);
recoveryKeyProperty.set(recoveryKey);
window.setScene(recoveryKeyScene.get());
}).onError(IOException.class, e -> {
// TODO show generic error screen
LOG.error("", e);
}).andFinally(() -> {
processing.set(false);
}).runOnce(executor);
}
private void showSuccessScene() {
Path pathToVault = vaultPathProperty.get();
processing.set(true);
Tasks.create(() -> {
initializeVault(pathToVault, password.get());
}).onSuccess(() -> {
initializationSucceeded(pathToVault);
window.setScene(successScene.get());
}).onError(IOException.class, e -> { }).onError(IOException.class, e -> {
// TODO show generic error screen // TODO show generic error screen
LOG.error("", e); LOG.error("", e);
@@ -176,12 +181,10 @@ public class CreateNewVaultPasswordController implements FxController {
LOG.info("Created vault at {}", path); LOG.info("Created vault at {}", path);
} }
private void initializationSucceeded(Path pathToVault, String recoveryKey) { private void initializationSucceeded(Path pathToVault) {
try { try {
Vault newVault = vaultListManager.add(pathToVault); Vault newVault = vaultListManager.add(pathToVault);
vaultProperty.set(newVault); vaultProperty.set(newVault);
recoveryKeyProperty.set(recoveryKey);
window.setScene(recoveryKeyScene.get());
} catch (NoSuchFileException e) { } catch (NoSuchFileException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
@@ -197,14 +200,6 @@ public class CreateNewVaultPasswordController implements FxController {
return vaultNameProperty; return vaultNameProperty;
} }
public IntegerProperty passwordStrengthProperty() {
return passwordStrength;
}
public int getPasswordStrength() {
return passwordStrength.get();
}
public BooleanProperty readyToCreateVaultProperty() { public BooleanProperty readyToCreateVaultProperty() {
return readyToCreateVault; return readyToCreateVault;
} }
@@ -1,7 +1,6 @@
package org.cryptomator.ui.addvaultwizard; package org.cryptomator.ui.addvaultwizard;
import dagger.Lazy; import dagger.Lazy;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -10,33 +9,20 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
public class CreateNewVaultRecoveryKeyController implements FxController { public class CreateNewVaultRecoveryKeyController implements FxController {
private final Stage window; private final Stage window;
private final Lazy<Scene> successScene; private final Lazy<Scene> successScene;
private final StringProperty recoveryKeyProperty;
@Inject @Inject
CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, @Named("recoveryKey")StringProperty recoveryKey) { CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene) {
this.window = window; this.window = window;
this.successScene = successScene; this.successScene = successScene;
this.recoveryKeyProperty = recoveryKey;
} }
@FXML @FXML
public void next() { public void next() {
window.setScene(successScene.get()); window.setScene(successScene.get());
} }
/* Getter/Setter */
public String getRecoveryKey() {
return recoveryKeyProperty.get();
}
public StringProperty recoveryKeyProperty() {
return recoveryKeyProperty;
}
} }
@@ -13,20 +13,30 @@ import java.nio.file.Paths;
public class LocationPresets { public class LocationPresets {
private static final String USER_HOME = System.getProperty("user.home"); private static final String USER_HOME = System.getProperty("user.home");
private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents"};
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"}; private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"}; private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
private final ReadOnlyObjectProperty<Path> dropboxLocation; private final ReadOnlyObjectProperty<Path> dropboxLocation;
private final ReadOnlyObjectProperty<Path> gdriveLocation; private final ReadOnlyObjectProperty<Path> gdriveLocation;
private final ReadOnlyObjectProperty<Path> onedriveLocation;
private final BooleanBinding foundIclouddrive;
private final BooleanBinding foundDropbox; private final BooleanBinding foundDropbox;
private final BooleanBinding foundGdrive; private final BooleanBinding foundGdrive;
private final BooleanBinding foundOnedrive;
@Inject @Inject
public LocationPresets() { public LocationPresets() {
this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS)); this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS)); this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
this.foundIclouddrive = iclouddriveLocation.isNotNull();
this.foundDropbox = dropboxLocation.isNotNull(); this.foundDropbox = dropboxLocation.isNotNull();
this.foundGdrive = gdriveLocation.isNotNull(); this.foundGdrive = gdriveLocation.isNotNull();
this.foundOnedrive = onedriveLocation.isNotNull();
} }
private static Path existingWritablePath(String... candidates) { private static Path existingWritablePath(String... candidates) {
@@ -49,6 +59,22 @@ public class LocationPresets {
/* Observables */ /* Observables */
public ReadOnlyObjectProperty<Path> iclouddriveLocationProperty() {
return iclouddriveLocation;
}
public Path getIclouddriveLocation() {
return iclouddriveLocation.get();
}
public BooleanBinding foundIclouddriveProperty() {
return foundIclouddrive;
}
public boolean isFoundIclouddrive() {
return foundIclouddrive.get();
}
public ReadOnlyObjectProperty<Path> dropboxLocationProperty() { public ReadOnlyObjectProperty<Path> dropboxLocationProperty() {
return dropboxLocation; return dropboxLocation;
} }
@@ -73,7 +99,7 @@ public class LocationPresets {
return gdriveLocation.get(); return gdriveLocation.get();
} }
public BooleanBinding froundGdriveProperty() { public BooleanBinding foundGdriveProperty() {
return foundGdrive; return foundGdrive;
} }
@@ -81,4 +107,20 @@ public class LocationPresets {
return foundGdrive.get(); return foundGdrive.get();
} }
public ReadOnlyObjectProperty<Path> onedriveLocationProperty() {
return onedriveLocation;
}
public Path getOnedriveLocation() {
return onedriveLocation.get();
}
public BooleanBinding foundOnedriveProperty() {
return foundOnedrive;
}
public boolean isFoundOnedrive() {
return foundOnedrive.get();
}
} }
@@ -3,6 +3,7 @@ package org.cryptomator.ui.changepassword;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException; import java.io.IOException;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -33,47 +35,24 @@ public class ChangePasswordController implements FxController {
private final Stage window; private final Stage window;
private final Vault vault; private final Vault vault;
private final ResourceBundle resourceBundle; private final ObjectProperty<CharSequence> newPassword;
private final PasswordStrengthUtil strengthRater;
private final IntegerProperty passwordStrength;
public NiceSecurePasswordField oldPasswordField; public NiceSecurePasswordField oldPasswordField;
public NiceSecurePasswordField newPasswordField;
public NiceSecurePasswordField reenterPasswordField;
public Label passwordStrengthLabel;
public HBox passwordMatchBox;
public FontAwesome5IconView checkmark;
public FontAwesome5IconView cross;
public Label passwordMatchLabel;
public CheckBox finalConfirmationCheckbox; public CheckBox finalConfirmationCheckbox;
public Button finishButton; public Button finishButton;
@Inject @Inject
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) { public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.resourceBundle = resourceBundle; this.newPassword = newPassword;
this.strengthRater = strengthRater;
this.passwordStrength = new SimpleIntegerProperty(-1);
} }
@FXML @FXML
public void initialize() { public void initialize() {
//binds the actual strength value to the rating of the password util BooleanBinding hasNotConfirmedCheckbox = finalConfirmationCheckbox.selectedProperty().not();
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty())); BooleanBinding isInvalidNewPassword = Bindings.createBooleanBinding(() -> newPassword.get() == null || newPassword.get().length() == 0, newPassword);
//binding indicating if the passwords not match finishButton.disableProperty().bind(hasNotConfirmedCheckbox.or(isInvalidNewPassword));
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(newPasswordField.getCharacters(), reenterPasswordField.getCharacters()) == 0, newPasswordField.textProperty(), reenterPasswordField.textProperty());
BooleanBinding reenterFieldNotEmpty = reenterPasswordField.textProperty().isNotEmpty();
//disable the finish button when passwords do not match or one is empty
finishButton.disableProperty().bind(reenterFieldNotEmpty.not().or(passwordsMatch.not()).or(finalConfirmationCheckbox.selectedProperty().not()));
//make match indicator invisible when passwords do not match or one is empty
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
checkmark.managedProperty().bind(checkmark.visibleProperty());
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
cross.managedProperty().bind(cross.visibleProperty());
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("changepassword.passwordsMatch")).otherwise(resourceBundle.getString("changepassword.passwordsDoNotMatch")));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
} }
@FXML @FXML
@@ -84,15 +63,15 @@ public class ChangePasswordController implements FxController {
@FXML @FXML
public void finish() { public void finish() {
try { try {
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters()); CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
LOG.info("Successful changed password for {}", vault.getDisplayableName()); LOG.info("Successful changed password for {}", vault.getDisplayableName());
window.close(); window.close();
} catch (IOException e) { } catch (IOException e) {
//TODO // TODO show generic error screen
LOG.error("IO error occured during password change. Unable to perform operation.", e); LOG.error("IO error occured during password change. Unable to perform operation.", e);
e.printStackTrace(); e.printStackTrace();
} catch (InvalidPassphraseException e) { } catch (InvalidPassphraseException e) {
//TODO // TODO shake
LOG.info("Wrong old password."); LOG.info("Wrong old password.");
} }
} }
@@ -103,11 +82,4 @@ public class ChangePasswordController implements FxController {
return vault; return vault;
} }
public IntegerProperty passwordStrengthProperty() {
return passwordStrength;
}
public int getPasswordStrength() {
return passwordStrength.get();
}
} }
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Modality; import javafx.stage.Modality;
@@ -14,9 +16,12 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import java.nio.CharBuffer;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -24,6 +29,13 @@ import java.util.ResourceBundle;
@Module @Module
abstract class ChangePasswordModule { abstract class ChangePasswordModule {
@Provides
@ChangePasswordScoped
@Named("newPassword")
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
return new SimpleObjectProperty<>("");
}
@Provides @Provides
@ChangePasswordWindow @ChangePasswordWindow
@ChangePasswordScoped @ChangePasswordScoped
@@ -59,4 +71,11 @@ abstract class ChangePasswordModule {
@FxControllerKey(ChangePasswordController.class) @FxControllerKey(ChangePasswordController.class)
abstract FxController bindUnlockController(ChangePasswordController controller); abstract FxController bindUnlockController(ChangePasswordController controller);
@Provides
@IntoMap
@FxControllerKey(NewPasswordController.class)
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
return new NewPasswordController(resourceBundle, strengthRater, password);
}
} }
@@ -8,6 +8,7 @@ import javafx.scene.input.KeyCombination;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.Window; import javafx.stage.Window;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.fxapp.FxApplicationScoped; import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
@@ -19,16 +20,25 @@ public class DefaultSceneFactory implements Function<Parent, Scene> {
protected static final KeyCodeCombination ALT_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN); protected static final KeyCodeCombination ALT_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN);
protected static final KeyCodeCombination SHORTCUT_W = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_W = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
protected final Settings settings;
@Inject @Inject
public DefaultSceneFactory() {} public DefaultSceneFactory(Settings settings) {
this.settings = settings;
}
@Override @Override
public Scene apply(Parent root) { public Scene apply(Parent root) {
Scene scene = new Scene(root); Scene scene = new Scene(root);
configureRoot(root);
configureScene(scene); configureScene(scene);
return scene; return scene;
} }
protected void configureRoot(Parent root) {
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation());
}
protected void configureScene(Scene scene) { protected void configureScene(Scene scene) {
scene.windowProperty().addListener(observable -> { scene.windowProperty().addListener(observable -> {
Window window = scene.getWindow(); Window window = scene.getWindow();
@@ -18,9 +18,11 @@ public enum FxmlFile {
PREFERENCES("/fxml/preferences.fxml"), // PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), // QUIT("/fxml/quit.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), // RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
RECOVERYKEY_DISPLAY("/fxml/recoverykey_display.fxml"), // RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), // REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK("/fxml/unlock.fxml"), UNLOCK("/fxml/unlock.fxml"),
UNLOCK_GENERIC_ERROR("/fxml/unlock_generic_error.fxml"), //
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), // UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
VAULT_OPTIONS("/fxml/vault_options.fxml"), // VAULT_OPTIONS("/fxml/vault_options.fxml"), //
WRONGFILEALERT("/fxml/wrongfilealert.fxml"); WRONGFILEALERT("/fxml/wrongfilealert.fxml");
@@ -0,0 +1,74 @@
package org.cryptomator.ui.common;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.fxmisc.easybind.EasyBind;
import java.util.ResourceBundle;
public class NewPasswordController implements FxController {
private final ResourceBundle resourceBundle;
private final PasswordStrengthUtil strengthRater;
private final ObjectProperty<CharSequence> password;
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1);
public NiceSecurePasswordField passwordField;
public NiceSecurePasswordField reenterField;
public Label passwordStrengthLabel;
public Label passwordMatchLabel;
public FontAwesome5IconView checkmark;
public FontAwesome5IconView cross;
public NewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ObjectProperty<CharSequence> password) {
this.resourceBundle = resourceBundle;
this.strengthRater = strengthRater;
this.password = password;
}
@FXML
public void initialize() {
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::hasSamePasswordInBothFields, passwordField.textProperty(), reenterField.textProperty());
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
passwordMatchLabel.visibleProperty().bind(reenterFieldNotEmpty);
passwordMatchLabel.graphicProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(checkmark).otherwise(cross));
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("newPassword.passwordsMatch")).otherwise(resourceBundle.getString("newPassword.passwordsDoNotMatch")));
passwordField.textProperty().addListener(this::passwordsDidChange);
reenterField.textProperty().addListener(this::passwordsDidChange);
}
private void passwordsDidChange(@SuppressWarnings("unused") Observable observable) {
if (hasSamePasswordInBothFields() && strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters())) {
password.set(passwordField.getCharacters());
} else {
password.set("");
}
}
private boolean hasSamePasswordInBothFields() {
return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0;
}
/* Getter/Setter */
public IntegerProperty passwordStrengthProperty() {
return passwordStrength;
}
public int getPasswordStrength() {
return passwordStrength.get();
}
}
@@ -8,12 +8,11 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.ui.common; package org.cryptomator.ui.common;
import com.google.common.base.Strings;
import com.nulabinc.zxcvbn.Zxcvbn; import com.nulabinc.zxcvbn.Zxcvbn;
import org.cryptomator.common.Environment;
import org.cryptomator.ui.fxapp.FxApplicationScoped; import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -22,30 +21,37 @@ public class PasswordStrengthUtil {
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel."; private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
private static final List<String> SANITIZED_INPUTS = List.of("cryptomator");
private final Zxcvbn zxcvbn;
private final List<String> sanitizedInputs;
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final int minPwLength;
private final Zxcvbn zxcvbn;
@Inject @Inject
public PasswordStrengthUtil(ResourceBundle resourceBundle) { public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) {
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.minPwLength = environment.getMinPwLength();
this.zxcvbn = new Zxcvbn(); this.zxcvbn = new Zxcvbn();
this.sanitizedInputs = List.of("cryptomator");
} }
public int computeRate(String password) { public boolean fulfillsMinimumRequirements(CharSequence password) {
if (Strings.isNullOrEmpty(password)) { return password.length() >= minPwLength;
}
public int computeRate(CharSequence password) {
if (password == null || password.length() < minPwLength) {
return -1; return -1;
} else { } else {
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length()); int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore(); return zxcvbn.measure(password.subSequence(0, numCharsToRate), SANITIZED_INPUTS).getScore();
} }
} }
public String getStrengthDescription(Number score) { public String getStrengthDescription(Number score) {
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) { if (score.intValue() == -1) {
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue()); return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength);
} else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue());
} else { } else {
return ""; return "";
} }
@@ -0,0 +1,28 @@
package org.cryptomator.ui.common;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
public class StackTraceController implements FxController {
private final String stackTrace;
public StackTraceController(Exception cause) {
this.stackTrace = provideStackTrace(cause);
}
static String provideStackTrace(Exception cause) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cause.printStackTrace(new PrintStream(baos));
return baos.toString(StandardCharsets.UTF_8);
}
/* Getter/Setter */
public String getStackTrace() {
return stackTrace;
}
}
@@ -0,0 +1,190 @@
package org.cryptomator.ui.common;
import javafx.concurrent.Task;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
@FxApplicationScoped
public class VaultService {
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
private final ExecutorService executorService;
@Inject
public VaultService(ExecutorService executorService) {
this.executorService = executorService;
}
public void reveal(Vault vault) {
executorService.execute(createRevealTask(vault));
}
/**
* Creates but doesn't start a reveal task.
*
* @param vault The vault to reveal
*/
public Task<Vault> createRevealTask(Vault vault) {
Task<Vault> task = new RevealVaultTask(vault);
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayableName()));
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayableName(), evt.getSource().getException()));
return task;
}
/**
* Locks a vault in a background thread.
*
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
*/
public void lock(Vault vault, boolean forced) {
executorService.execute(createLockTask(vault, forced));
}
/**
* Creates but doesn't start a lock task.
*
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
*/
public Task<Vault> createLockTask(Vault vault, boolean forced) {
Task<Vault> task = new LockVaultTask(vault, forced);
task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayableName()));
task.setOnFailed(evt -> LOG.error("Failed to lock " + vault.getDisplayableName(), evt.getSource().getException()));
return task;
}
/**
* Locks all given vaults in a background thread.
*
* @param vaults The vaults to lock
* @param forced Whether to attempt a forced lock
*/
public void lockAll(Collection<Vault> vaults, boolean forced) {
executorService.execute(createLockAllTask(vaults, forced));
}
/**
* Creates but doesn't start a lock-all task.
*
* @param vaults The list of vaults to be locked
* @param forced Whether to attempt a forced lock
* @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask
*/
public Task<Collection<Vault>> createLockAllTask(Collection<Vault> vaults, boolean forced) {
List<Task<Vault>> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList());
lockTasks.forEach(executorService::execute);
Task<Collection<Vault>> task = new WaitForTasksTask(lockTasks);
String vaultNames = vaults.stream().map(Vault::getDisplayableName).collect(Collectors.joining(", "));
task.setOnSucceeded(evt -> LOG.info("Locked {}", vaultNames));
task.setOnFailed(evt -> LOG.error("Failed to lock vaults " + vaultNames, evt.getSource().getException()));
return task;
}
private static class RevealVaultTask extends Task<Vault> {
private final Vault vault;
/**
* @param vault The vault to lock
*/
public RevealVaultTask(Vault vault) {
this.vault = vault;
}
@Override
protected Vault call() throws Volume.VolumeException {
vault.reveal();
return vault;
}
}
/**
* A task that waits for completion of multiple other tasks
*/
private static class WaitForTasksTask extends Task<Collection<Vault>> {
private final Collection<Task<Vault>> startedTasks;
public WaitForTasksTask(Collection<Task<Vault>> tasks) {
this.startedTasks = List.copyOf(tasks);
}
@Override
protected Collection<Vault> call() throws Exception {
Iterator<Task<Vault>> remainingTasks = startedTasks.iterator();
Collection<Vault> completed = new ArrayList<>();
try {
// wait for all tasks:
while (remainingTasks.hasNext()) {
Vault lockedVault = remainingTasks.next().get();
completed.add(lockedVault);
}
} catch (ExecutionException e) {
// cancel all remaining:
while (remainingTasks.hasNext()) {
remainingTasks.next().cancel(true);
}
throw e;
}
return List.copyOf(completed);
}
}
/**
* A task that locks a vault
*/
private static class LockVaultTask extends Task<Vault> {
private final Vault vault;
private final boolean forced;
/**
* @param vault The vault to lock
* @param forced Whether to attempt a forced lock
*/
public LockVaultTask(Vault vault, boolean forced) {
this.vault = vault;
this.forced = forced;
}
@Override
protected Vault call() throws Volume.VolumeException {
vault.lock(forced);
return vault;
}
@Override
protected void scheduled() {
vault.setState(VaultState.PROCESSING);
}
@Override
protected void succeeded() {
vault.setState(VaultState.LOCKED);
}
@Override
protected void failed() {
vault.setState(VaultState.UNLOCKED);
}
}
}
@@ -9,23 +9,29 @@ public enum FontAwesome5Icon {
CHECK("\uF00C"), // CHECK("\uF00C"), //
COG("\uF013"), // COG("\uF013"), //
COGS("\uF085"), // COGS("\uF085"), //
COPY("\uF0C5"), //
EXCLAMATION("\uF12A"), EXCLAMATION("\uF12A"),
EXCLAMATION_CIRCLE("\uF06A"), //
EXCLAMATION_TRIANGLE("\uF071"), // EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), // EYE("\uF06E"), //
EYE_SLASH("\uF070"), // EYE_SLASH("\uF070"), //
FILE_IMPORT("\uF56F"), // FILE_IMPORT("\uF56F"), //
FOLDER_OPEN("\uF07C"), // FOLDER_OPEN("\uF07C"), //
HAND_HOLDING_HEART("\uF4BE"), //
HEART("\uF004"), //
HDD("\uF0A0"), // HDD("\uF0A0"), //
KEY("\uF084"), // KEY("\uF084"), //
LINK("\uF0C1"), //
LOCK_ALT("\uF30D"), // LOCK_ALT("\uF30D"), //
LOCK_OPEN_ALT("\uF3C2"), // LOCK_OPEN_ALT("\uF3C2"), //
MINUS("\uF068"), //
PLUS("\uF067"), // PLUS("\uF067"), //
PRINT("\uF02F"), //
QUESTION("\uF128"), // QUESTION("\uF128"), //
SPARKLES("\uF890"), // SPARKLES("\uF890"), //
SPINNER("\uF110"), // SPINNER("\uF110"), //
SYNC("\uF021"), // SYNC("\uF021"), //
TIMES("\uF00D"), // TIMES("\uF00D"), //
USER_CROWN("\uF6A4"), //
WRENCH("\uF0AD"), // WRENCH("\uF0AD"), //
; ;
@@ -89,7 +89,7 @@ public class NiceSecurePasswordField extends StackPane {
} }
public void swipe() { public void swipe() {
passwordField.swipe();; passwordField.swipe();
} }
public void selectAll() { public void selectAll() {
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableSet; import javafx.collections.ObservableSet;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme; import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
@@ -16,8 +17,10 @@ import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiAppearance; import org.cryptomator.jni.MacApplicationUiAppearance;
import org.cryptomator.jni.MacApplicationUiState; import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions; import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent; import org.cryptomator.ui.unlock.UnlockComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -38,17 +41,21 @@ public class FxApplication extends Application {
private final UnlockComponent.Builder unlockWindowBuilder; private final UnlockComponent.Builder unlockWindowBuilder;
private final QuitComponent.Builder quitWindowBuilder; private final QuitComponent.Builder quitWindowBuilder;
private final Optional<MacFunctions> macFunctions; private final Optional<MacFunctions> macFunctions;
private final VaultService vaultService;
private final LicenseHolder licenseHolder;
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet(); private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages); private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
@Inject @Inject
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions) { FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions, VaultService vaultService, LicenseHolder licenseHolder) {
this.settings = settings; this.settings = settings;
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow; this.preferencesWindow = preferencesWindow;
this.unlockWindowBuilder = unlockWindowBuilder; this.unlockWindowBuilder = unlockWindowBuilder;
this.quitWindowBuilder = quitWindowBuilder; this.quitWindowBuilder = quitWindowBuilder;
this.macFunctions = macFunctions; this.macFunctions = macFunctions;
this.vaultService = vaultService;
this.licenseHolder = licenseHolder;
} }
public void start() { public void start() {
@@ -79,9 +86,9 @@ public class FxApplication extends Application {
} }
} }
public void showPreferencesWindow() { public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
Platform.runLater(() -> { Platform.runLater(() -> {
Stage stage = preferencesWindow.get().showPreferencesWindow(); Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
addVisibleStage(stage); addVisibleStage(stage);
LOG.debug("Showing Preferences"); LOG.debug("Showing Preferences");
}); });
@@ -111,11 +118,16 @@ public class FxApplication extends Application {
}); });
} }
public VaultService getVaultService() {
return vaultService;
}
private void themeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { private void themeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
loadSelectedStyleSheet(newValue); loadSelectedStyleSheet(newValue);
} }
private void loadSelectedStyleSheet(UiTheme theme) { private void loadSelectedStyleSheet(UiTheme desiredTheme) {
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) { switch (theme) {
// case CUSTOM: // case CUSTOM:
// // TODO // // TODO
@@ -5,8 +5,10 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.image.Image; import javafx.scene.image.Image;
@@ -45,6 +47,9 @@ abstract class FxApplicationModule {
} }
} }
@Binds
abstract Application bindApplication(FxApplication application);
@Provides @Provides
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) { static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
return builder.build(); return builder.build();
@@ -1,30 +1,24 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import java.io.File; import java.io.File;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
@MainWindowScoped @MainWindowScoped
public class MainWindowController implements FxController { public class MainWindowController implements FxController {
@@ -32,27 +26,14 @@ public class MainWindowController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class); private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final Stage window;
private final FxApplication application;
private final boolean minimizeToSysTray;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final VaultListManager vaultListManager; private final VaultListManager vaultListManager;
private final WrongFileAlertComponent.Builder wrongFileAlert; private final WrongFileAlertComponent.Builder wrongFileAlert;
public HBox titleBar; private final BooleanProperty draggingOver = new SimpleBooleanProperty();
public VBox root; private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
public Pane dragAndDropIndicator; public StackPane root;
public Region resizer;
private double xOffset;
private double yOffset;
@Inject @Inject
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) { public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.vaultListManager = vaultListManager; this.vaultListManager = vaultListManager;
this.wrongFileAlert = wrongFileAlert; this.wrongFileAlert = wrongFileAlert;
} }
@@ -60,77 +41,70 @@ public class MainWindowController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
LOG.debug("init MainWindowController"); LOG.debug("init MainWindowController");
titleBar.setOnMousePressed(event -> { root.setOnDragEntered(this::handleDragEvent);
xOffset = event.getSceneX(); root.setOnDragOver(this::handleDragEvent);
yOffset = event.getSceneY(); root.setOnDragDropped(this::handleDragEvent);
}); root.setOnDragExited(this::handleDragEvent);
titleBar.setOnMouseDragged(event -> {
window.setX(event.getScreenX() - xOffset);
window.setY(event.getScreenY() - yOffset);
});
resizer.setOnMouseDragged(event -> {
// we know for a fact that window is borderless. i.e. the scene starts at 0/0 of the window.
window.setWidth(event.getSceneX());
window.setHeight(event.getSceneY());
});
updateChecker.automaticallyCheckForUpdatesIfEnabled();
dragAndDropIndicator.setVisible(false);
root.setOnDragOver(event -> {
if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
/* allow for both copying and moving, whatever user chooses */
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
dragAndDropIndicator.setVisible(true);
}
event.consume();
});
root.setOnDragExited(event -> dragAndDropIndicator.setVisible(false));
root.setOnDragDropped(event -> {
if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
/* allow for both copying and moving, whatever user chooses */
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
Collection<Vault> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).flatMap(this::addVault).collect(Collectors.toSet());
if (vaultPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
}
}
event.consume();
});
} }
private Stream<Vault> addVault(Path pathToVault) { private void handleDragEvent(DragEvent event) {
if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
draggingOver.set(true);
} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.ANY);
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
if (vaultPaths.isEmpty()) {
wrongFileAlert.build().showWrongFileAlertWindow();
} else {
vaultPaths.forEach(this::addVault);
}
event.setDropCompleted(!vaultPaths.isEmpty());
event.consume();
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
draggingOver.set(false);
draggingVaultOver.set(false);
}
}
private boolean containsVault(Path path) {
if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
return true;
} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
return true;
} else {
return false;
}
}
private void addVault(Path pathToVault) {
try { try {
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) { if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
return Stream.of(vaultListManager.add(pathToVault.getParent())); vaultListManager.add(pathToVault.getParent());
} else { } else {
return Stream.of(vaultListManager.add(pathToVault)); vaultListManager.add(pathToVault);
} }
} catch (NoSuchFileException e) { } catch (NoSuchFileException e) {
LOG.debug("Not a vault: {}", pathToVault); LOG.debug("Not a vault: {}", pathToVault);
} }
return Stream.empty();
}
@FXML
public void close() {
if (minimizeToSysTray) {
window.close();
} else {
window.setIconified(true);
}
}
@FXML
public void showPreferences() {
application.showPreferencesWindow();
} }
/* Getter/Setter */ /* Getter/Setter */
public BooleanBinding updateAvailableProperty() { public BooleanProperty draggingOverProperty() {
return updateAvailable; return draggingOver;
} }
public boolean isUpdateAvailable() { public boolean isDraggingOver() {
return updateAvailable.get(); return draggingOver.get();
}
public BooleanProperty draggingVaultOverProperty() {
return draggingVaultOver;
}
public boolean isDraggingVaultOver() {
return draggingVaultOver.get();
} }
} }
@@ -68,6 +68,16 @@ abstract class MainWindowModule {
@FxControllerKey(MainWindowController.class) @FxControllerKey(MainWindowController.class)
abstract FxController bindMainWindowController(MainWindowController controller); abstract FxController bindMainWindowController(MainWindowController controller);
@Binds
@IntoMap
@FxControllerKey(MainWindowTitleController.class)
abstract FxController bindMainWindowTitleController(MainWindowTitleController controller);
@Binds
@IntoMap
@FxControllerKey(ResizeController.class)
abstract FxController bindResizeController(ResizeController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(VaultListController.class) @FxControllerKey(VaultListController.class)
@@ -7,6 +7,7 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyCombination;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import javax.inject.Inject; import javax.inject.Inject;
@@ -16,21 +17,22 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
private final Lazy<MainWindowController> mainWindowController; private final Lazy<MainWindowTitleController> mainWindowTitleController;
private final Lazy<VaultListController> vaultListController; private final Lazy<VaultListController> vaultListController;
@Inject @Inject
public MainWindowSceneFactory(Lazy<MainWindowController> mainWindowController, Lazy<VaultListController> vaultListController) { public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) {
this.mainWindowController = mainWindowController; super(settings);
this.mainWindowTitleController = mainWindowTitleController;
this.vaultListController = vaultListController; this.vaultListController = vaultListController;
} }
@Override @Override
protected void setupDefaultAccelerators(Scene scene, Stage stage) { protected void setupDefaultAccelerators(Scene scene, Stage stage) {
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
scene.getAccelerators().put(ALT_F4, mainWindowController.get()::close); scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
} else { } else {
scene.getAccelerators().put(SHORTCUT_W, mainWindowController.get()::close); scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
} }
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault); scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
} }
@@ -0,0 +1,95 @@
package org.cryptomator.ui.mainwindow;
import javafx.beans.binding.BooleanBinding;
import javafx.fxml.FXML;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
@MainWindowScoped
public class MainWindowTitleController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
public HBox titleBar;
private final Stage window;
private final FxApplication application;
private final boolean minimizeToSysTray;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
private double xOffset;
private double yOffset;
@Inject
MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.licenseHolder = licenseHolder;
}
@FXML
public void initialize() {
LOG.debug("init MainWindowTitleController");
updateChecker.automaticallyCheckForUpdatesIfEnabled();
titleBar.setOnMousePressed(event -> {
xOffset = event.getSceneX();
yOffset = event.getSceneY();
});
titleBar.setOnMouseDragged(event -> {
window.setX(event.getScreenX() - xOffset);
window.setY(event.getScreenY() - yOffset);
});
}
@FXML
public void close() {
if (minimizeToSysTray) {
window.close();
} else {
window.setIconified(true);
}
}
@FXML
public void showPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showDonationKeyPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
}
/* Getter/Setter */
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean isUpdateAvailable() {
return updateAvailable.get();
}
}
@@ -0,0 +1,102 @@
package org.cryptomator.ui.mainwindow;
import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@MainWindow
public class ResizeController implements FxController {
private final Stage window;
public Region tlResizer;
public Region trResizer;
public Region blResizer;
public Region brResizer;
private double origX, origY, origW, origH;
@Inject
ResizeController(@MainWindow Stage window) {
this.window = window;
// TODO inject settings and save current position and size
}
@FXML
public void initialize() {
tlResizer.setOnMousePressed(this::startResize);
trResizer.setOnMousePressed(this::startResize);
blResizer.setOnMousePressed(this::startResize);
brResizer.setOnMousePressed(this::startResize);
tlResizer.setOnMouseDragged(this::resizeTopLeft);
trResizer.setOnMouseDragged(this::resizeTopRight);
blResizer.setOnMouseDragged(this::resizeBottomLeft);
brResizer.setOnMouseDragged(this::resizeBottomRight);
}
private void startResize(MouseEvent evt) {
origX = window.getX();
origY = window.getY();
origW = window.getWidth();
origH = window.getHeight();
}
private void resizeTopLeft(MouseEvent evt) {
resizeTop(evt);
resizeLeft(evt);
}
private void resizeTopRight(MouseEvent evt) {
resizeTop(evt);
resizeRight(evt);
}
private void resizeBottomLeft(MouseEvent evt) {
resizeBottom(evt);
resizeLeft(evt);
}
private void resizeBottomRight(MouseEvent evt) {
resizeBottom(evt);
resizeRight(evt);
}
private void resizeTop(MouseEvent evt) {
double newY = evt.getScreenY();
double dy = newY - origY;
double newH = origH - dy;
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setY(newY);
window.setHeight(newH);
}
}
private void resizeLeft(MouseEvent evt) {
double newX = evt.getScreenX();
double dx = newX - origX;
double newW = origW - dx;
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setX(newX);
window.setWidth(newW);
}
}
private void resizeBottom(MouseEvent evt) {
double newH = evt.getSceneY();
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setHeight(newH);
}
}
private void resizeRight(MouseEvent evt) {
double newW = evt.getSceneX();
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setWidth(newW);
}
}
}
@@ -4,53 +4,32 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.concurrent.ExecutorService;
@MainWindowScoped @MainWindowScoped
public class VaultDetailUnlockedController implements FxController { public class VaultDetailUnlockedController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
private final ReadOnlyObjectProperty<Vault> vault; private final ReadOnlyObjectProperty<Vault> vault;
private final ExecutorService executor; private final VaultService vaultService;
@Inject @Inject
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, ExecutorService executor) { public VaultDetailUnlockedController(ObjectProperty<Vault> vault, VaultService vaultService) {
this.vault = vault; this.vault = vault;
this.executor = executor; this.vaultService = vaultService;
} }
@FXML @FXML
public void revealAccessLocation() { public void revealAccessLocation() {
try { vaultService.reveal(vault.get());
vault.get().reveal();
} catch (Volume.VolumeException e) {
LOG.error("Failed to reveal vault.", e);
}
} }
@FXML @FXML
public void lock() { public void lock() {
Vault v = vault.get(); vaultService.lock(vault.get(), false);
v.setState(VaultState.PROCESSING); // TODO count lock attempts, and allow forced lock
Tasks.create(() -> {
v.lock(false);
}).onSuccess(() -> {
LOG.trace("Regular unmount succeeded.");
v.setState(VaultState.LOCKED);
}).onError(Exception.class, e -> {
v.setState(VaultState.UNLOCKED);
LOG.error("Regular unmount failed.", e);
// TODO
}).runOnce(executor);
} }
/* Getter/Setter */ /* Getter/Setter */
@@ -1,14 +1,18 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent;
@@ -23,7 +27,6 @@ public class VaultListController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class); private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
private final Stage window;
private final ObservableList<Vault> vaults; private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> selectedVault; private final ObjectProperty<Vault> selectedVault;
private final VaultListCellFactory cellFactory; private final VaultListCellFactory cellFactory;
@@ -34,8 +37,7 @@ public class VaultListController implements FxController {
public ListView<Vault> vaultList; public ListView<Vault> vaultList;
@Inject @Inject
VaultListController(@MainWindow Stage window, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) { VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
this.window = window;
this.vaults = vaults; this.vaults = vaults;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
this.cellFactory = cellFactory; this.cellFactory = cellFactory;
@@ -43,6 +45,7 @@ public class VaultListController implements FxController {
this.removeVault = removeVault; this.removeVault = removeVault;
this.noVaultSelected = selectedVault.isNull(); this.noVaultSelected = selectedVault.isNull();
this.emptyVaultList = Bindings.isEmpty(vaults); this.emptyVaultList = Bindings.isEmpty(vaults);
selectedVault.addListener(this::selectedVaultDidChange);
} }
public void initialize() { public void initialize() {
@@ -59,6 +62,23 @@ public class VaultListController implements FxController {
}); });
} }
private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
VaultState reportedState = newValue.getState();
switch (reportedState) {
case LOCKED:
case NEEDS_MIGRATION:
case MISSING:
VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
newValue.setState(determinedState);
break;
case ERROR:
case UNLOCKED:
case PROCESSING:
default:
// no-op
}
}
@FXML @FXML
public void didClickAddVault() { public void didClickAddVault() {
addVaultWizard.build().showAddVaultWizard(); addVaultWizard.build().showAddVaultWizard();
@@ -0,0 +1,43 @@
package org.cryptomator.ui.preferences;
import org.cryptomator.jni.MacFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
class AutoStartMacStrategy implements AutoStartStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AutoStartMacStrategy.class);
private final MacFunctions macFunctions;
public AutoStartMacStrategy(MacFunctions macFunctions) {
this.macFunctions = macFunctions;
}
@Override
public CompletionStage<Boolean> isAutoStartEnabled() {
boolean enabled = macFunctions.launchServices().isLoginItemEnabled();
return CompletableFuture.completedFuture(enabled);
}
@Override
public void enableAutoStart() throws TogglingAutoStartFailedException {
if (macFunctions.launchServices().enableLoginItem()) {
LOG.debug("Added login item.");
} else {
throw new TogglingAutoStartFailedException("Failed to add login item.");
}
}
@Override
public void disableAutoStart() throws TogglingAutoStartFailedException {
if (macFunctions.launchServices().disableLoginItem()) {
LOG.debug("Removed login item.");
} else {
throw new TogglingAutoStartFailedException("Failed to remove login item.");
}
}
}
@@ -0,0 +1,26 @@
package org.cryptomator.ui.preferences;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.jni.MacFunctions;
import java.util.Optional;
@Module
abstract class AutoStartModule {
@Provides
@PreferencesScoped
public static Optional<AutoStartStrategy> provideAutoStartStrategy(Optional<MacFunctions> macFunctions) {
if (SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent()) {
return Optional.of(new AutoStartMacStrategy(macFunctions.get()));
} else if (SystemUtils.IS_OS_WINDOWS) {
Optional<String> exeName = ProcessHandle.current().info().command();
return exeName.map(AutoStartWinStrategy::new);
} else {
return Optional.empty();
}
}
}
@@ -0,0 +1,24 @@
package org.cryptomator.ui.preferences;
import java.util.concurrent.CompletionStage;
public interface AutoStartStrategy {
CompletionStage<Boolean> isAutoStartEnabled();
void enableAutoStart() throws TogglingAutoStartFailedException;
void disableAutoStart() throws TogglingAutoStartFailedException;
class TogglingAutoStartFailedException extends Exception {
public TogglingAutoStartFailedException(String message) {
super(message);
}
public TogglingAutoStartFailedException(String message, Throwable cause) {
super(message, cause);
}
}
}
@@ -0,0 +1,91 @@
package org.cryptomator.ui.preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
class AutoStartWinStrategy implements AutoStartStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
private static final String AUTOSTART_VALUE = "Cryptomator";
private final String exePath;
public AutoStartWinStrategy(String exePath) {
this.exePath = exePath;
}
@Override
public CompletionStage<Boolean> isAutoStartEnabled() {
ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
"/v", AUTOSTART_VALUE);
try {
Process proc = regQuery.start();
return proc.onExit().thenApply(p -> p.exitValue() == 0);
} catch (IOException e) {
LOG.warn("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
return CompletableFuture.completedFuture(false);
}
}
@Override
public void enableAutoStart() throws TogglingAutoStartFailedException {
ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
"/v", AUTOSTART_VALUE, //
"/t", "REG_SZ", //
"/d", "\"" + exePath + "\"", //
"/f");
String command = regAdd.command().stream().collect(Collectors.joining(" "));
try {
Process proc = regAdd.start();
boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
if (finishedInTime) {
LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
} else {
throw new TogglingAutoStartFailedException("Adding registry value failed.");
}
} catch (IOException e) {
throw new TogglingAutoStartFailedException("Adding registry value failed. " + command, e);
}
}
@Override
public void disableAutoStart() throws TogglingAutoStartFailedException {
ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
"/v", AUTOSTART_VALUE, //
"/f");
String command = regRemove.command().stream().collect(Collectors.joining(" "));
try {
Process proc = regRemove.start();
boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
if (finishedInTime) {
LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
} else {
throw new TogglingAutoStartFailedException("Removing registry value failed.");
}
} catch (IOException e) {
throw new TogglingAutoStartFailedException("Removing registry value failed. " + command, e);
}
}
private static boolean waitForProcess(Process proc, int timeout, TimeUnit timeUnit) {
boolean finishedInTime = false;
try {
finishedInTime = proc.waitFor(timeout, timeUnit);
} catch (InterruptedException e) {
LOG.error("Timeout while reading registry", e);
Thread.currentThread().interrupt();
} finally {
if (!finishedInTime) {
proc.destroyForcibly();
}
}
return finishedInTime;
}
}
@@ -0,0 +1,45 @@
package org.cryptomator.ui.preferences;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@PreferencesScoped
public class DonationKeyPreferencesController implements FxController {
private static final String DONATION_URI = "https://store.cryptomator.org/desktop";
private final Application application;
private final LicenseHolder licenseHolder;
public TextArea donationKeyField;
@Inject
DonationKeyPreferencesController(Application application, LicenseHolder licenseHolder) {
this.application = application;
this.licenseHolder = licenseHolder;
}
@FXML
public void initialize() {
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
}
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
licenseHolder.validateAndStoreLicense(newValue);
}
@FXML
public void getDonationKey() {
application.getHostServices().showDocument(DONATION_URI);
}
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
}
@@ -1,27 +1,56 @@
package org.cryptomator.ui.preferences; package org.cryptomator.ui.preferences;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.geometry.NodeOrientation;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox; import javafx.scene.control.ChoiceBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme; import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
@PreferencesScoped @PreferencesScoped
public class GeneralPreferencesController implements FxController { public class GeneralPreferencesController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
private final Settings settings; private final Settings settings;
private final boolean trayMenuSupported;
private final Optional<AutoStartStrategy> autoStartStrategy;
private final LicenseHolder licenseHolder;
private final ExecutorService executor;
public ChoiceBox<UiTheme> themeChoiceBox; public ChoiceBox<UiTheme> themeChoiceBox;
public CheckBox startHiddenCheckbox; public CheckBox startHiddenCheckbox;
public CheckBox debugModeCheckbox; public CheckBox debugModeCheckbox;
public CheckBox autoStartCheckbox;
public ToggleGroup nodeOrientation;
public RadioButton nodeOrientationLtr;
public RadioButton nodeOrientationRtl;
@Inject @Inject
GeneralPreferencesController(Settings settings) { GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, LicenseHolder licenseHolder, ExecutorService executor) {
this.settings = settings; this.settings = settings;
this.trayMenuSupported = trayMenuSupported;
this.autoStartStrategy = autoStartStrategy;
this.licenseHolder = licenseHolder;
this.executor = executor;
} }
@FXML
public void initialize() { public void initialize() {
themeChoiceBox.getItems().addAll(UiTheme.values()); themeChoiceBox.getItems().addAll(UiTheme.values());
themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
@@ -30,6 +59,48 @@ public class GeneralPreferencesController implements FxController {
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
autoStartStrategy.ifPresent(autoStart -> {
autoStart.isAutoStartEnabled().thenAccept(enabled -> {
Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
});
});
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
}
public boolean isTrayMenuSupported() {
return this.trayMenuSupported;
}
public boolean isAutoStartSupported() {
return autoStartStrategy.isPresent();
}
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
if (nodeOrientationLtr.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT);
} else if (nodeOrientationRtl.equals(newValue)) {
settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT);
} else {
LOG.warn("Unexpected toggle option {}", newValue);
}
}
@FXML
public void toggleAutoStart() {
autoStartStrategy.ifPresent(autoStart -> {
boolean enableAutoStart = autoStartCheckbox.isSelected();
Task<Void> toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
toggleTask.setOnFailed(evt -> autoStartCheckbox.setSelected(!enableAutoStart)); // restore previous state
executor.execute(toggleTask);
});
}
public LicenseHolder getLicenseHolder() {
return licenseHolder;
} }
/* Helper classes */ /* Helper classes */
@@ -47,4 +118,25 @@ public class GeneralPreferencesController implements FxController {
} }
} }
private static class ToggleAutoStartTask extends Task<Void> {
private final AutoStartStrategy autoStart;
private final boolean enable;
public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
this.autoStart = autoStart;
this.enable = enable;
}
@Override
protected Void call() throws Exception {
if (enable) {
autoStart.enableAutoStart();
} else {
autoStart.disableAutoStart();
}
return null;
}
}
} }
@@ -7,9 +7,9 @@ package org.cryptomator.ui.preferences;
import dagger.Lazy; import dagger.Lazy;
import dagger.Subcomponent; import dagger.Subcomponent;
import javafx.beans.property.ObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
@FxmlScene(FxmlFile.PREFERENCES) @FxmlScene(FxmlFile.PREFERENCES)
Lazy<Scene> scene(); Lazy<Scene> scene();
default Stage showPreferencesWindow() { ObjectProperty<SelectedPreferencesTab> selectedTabProperty();
default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
selectedTabProperty().set(selectedTab);
Stage stage = window(); Stage stage = window();
stage.setScene(scene().get()); stage.setScene(scene().get());
stage.show(); stage.show();
@@ -1,6 +1,9 @@
package org.cryptomator.ui.preferences; package org.cryptomator.ui.preferences;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TabPane; import javafx.scene.control.TabPane;
@@ -15,26 +18,50 @@ import javax.inject.Inject;
public class PreferencesController implements FxController { public class PreferencesController implements FxController {
private final Stage window; private final Stage window;
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
private final BooleanBinding updateAvailable; private final BooleanBinding updateAvailable;
public TabPane tabPane; public TabPane tabPane;
public Tab generalTab; public Tab generalTab;
public Tab volumeTab;
public Tab updatesTab; public Tab updatesTab;
public Tab donationKeyTab;
@Inject @Inject
public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) { public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
this.window = window; this.window = window;
this.selectedTabProperty = selectedTabProperty;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
} }
@FXML @FXML
public void initialize() { public void initialize() {
window.setOnShowing(this::windowWillAppear); window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab());
} }
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) { private void selectChosenTab() {
if (updateAvailable.get()) { Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
tabPane.getSelectionModel().select(updatesTab); tabPane.getSelectionModel().select(toBeSelected);
}
private Tab getTabToSelect(SelectedPreferencesTab selectedTab) {
switch (selectedTab) {
case UPDATES:
return updatesTab;
case VOLUME:
return volumeTab;
case DONATION_KEY:
return donationKeyTab;
case GENERAL:
return generalTab;
case ANY:
default:
return updateAvailable.get() ? updatesTab : generalTab;
} }
} }
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
selectChosenTab();
}
} }
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -20,9 +22,15 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@Module @Module(includes = {AutoStartModule.class})
abstract class PreferencesModule { abstract class PreferencesModule {
@Provides
@PreferencesScoped
static ObjectProperty<SelectedPreferencesTab> provideSelectedTabProperty() {
return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
}
@Provides @Provides
@PreferencesWindow @PreferencesWindow
@PreferencesScoped @PreferencesScoped
@@ -70,4 +78,9 @@ abstract class PreferencesModule {
@FxControllerKey(VolumePreferencesController.class) @FxControllerKey(VolumePreferencesController.class)
abstract FxController bindVolumePreferencesController(VolumePreferencesController controller); abstract FxController bindVolumePreferencesController(VolumePreferencesController controller);
@Binds
@IntoMap
@FxControllerKey(DonationKeyPreferencesController.class)
abstract FxController bindDonationKeyPreferencesController(DonationKeyPreferencesController controller);
} }
@@ -0,0 +1,28 @@
package org.cryptomator.ui.preferences;
public enum SelectedPreferencesTab {
/**
* Let the controller decide which tab to show.
*/
ANY,
/**
* Show general tab
*/
GENERAL,
/**
* Show volume tab
*/
VOLUME,
/**
* Show updates tab
*/
UPDATES,
/**
* Show donation key tab
*/
DONATION_KEY,
}
@@ -16,6 +16,9 @@ import org.cryptomator.ui.common.FxController;
import javax.inject.Inject; import javax.inject.Inject;
/**
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
*/
@PreferencesScoped @PreferencesScoped
public class VolumePreferencesController implements FxController { public class VolumePreferencesController implements FxController {
@@ -1,26 +1,22 @@
package org.cryptomator.ui.quit; package org.cryptomator.ui.quit;
import javafx.application.Platform;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import java.awt.desktop.QuitResponse; import java.awt.desktop.QuitResponse;
import java.util.Iterator; import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
@QuitScoped @QuitScoped
public class QuitController implements FxController { public class QuitController implements FxController {
@@ -30,15 +26,17 @@ public class QuitController implements FxController {
private final Stage window; private final Stage window;
private final QuitResponse response; private final QuitResponse response;
private final ObservableList<Vault> unlockedVaults; private final ObservableList<Vault> unlockedVaults;
private final ExecutorService executor; private final ExecutorService executorService;
private final VaultService vaultService;
public Button lockAndQuitButton; public Button lockAndQuitButton;
@Inject @Inject
QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executor) { QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
this.window = window; this.window = window;
this.response = response; this.response = response;
this.unlockedVaults = vaults.filtered(Vault::isUnlocked); this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
this.executor = executor; this.executorService = executorService;
this.vaultService = vaultService;
} }
@FXML @FXML
@@ -53,83 +51,23 @@ public class QuitController implements FxController {
lockAndQuitButton.setDisable(true); lockAndQuitButton.setDisable(true);
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT); lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
Iterator<Vault> toBeLocked = List.copyOf(unlockedVaults).iterator(); Task<Collection<Vault>> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false);
ScheduledService<Void> lockAllService = new LockAllVaultsService(executor, toBeLocked); lockAllTask.setOnSucceeded(evt -> {
lockAllService.setOnSucceeded(evt -> { LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")));
if (!toBeLocked.hasNext()) { if (unlockedVaults.isEmpty()) {
window.close(); window.close();
response.performQuit(); response.performQuit();
} }
}); });
lockAllService.setOnFailed(evt -> { lockAllTask.setOnFailed(evt -> {
LOG.warn("Locking failed", lockAllTask.getException());
lockAndQuitButton.setDisable(false); lockAndQuitButton.setDisable(false);
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY); lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) // TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163 // see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
response.cancelQuit(); response.cancelQuit();
}); });
lockAllService.start(); executorService.execute(lockAllTask);
}
/**
* @param vault The vault to lock
* @return Task that tries to lock the given vault gracefully.
*/
private Task<Void> createGracefulLockTask(Vault vault) {
Task task = new Task<Void>() {
@Override
protected Void call() throws Volume.VolumeException {
vault.lock(false);
LOG.info("Locked {}", vault.getDisplayableName());
return null;
}
};
task.setOnScheduled(evt -> {
vault.setState(VaultState.PROCESSING);
});
task.setOnSucceeded(evt -> {
vault.setState(VaultState.LOCKED);
});
task.setOnFailed(evt -> {
LOG.warn("Failed to lock vault", vault);
});
return task;
}
/**
* @return Task that succeeds immediately
*/
private Task<Void> createNoopTask() {
return new Task<>() {
@Override
protected Void call() {
return null;
}
};
}
private class LockAllVaultsService extends ScheduledService<Void> {
private final Iterator<Vault> vaultsToLock;
public LockAllVaultsService(Executor executor, Iterator<Vault> vaultsToLock) {
this.vaultsToLock = vaultsToLock;
setExecutor(executor);
setRestartOnFailure(false);
}
@Override
protected Task<Void> createTask() {
assert Platform.isFxApplicationThread();
if (vaultsToLock.hasNext()) {
return createGracefulLockTask(vaultsToLock.next());
} else {
// This should be unreachable code, since vaultsToLock is only accessed on the FX App Thread.
// But if quitting the application takes longer for any reason, this service should shut down properly
reset();
return createNoopTask();
}
}
} }
} }
@@ -40,7 +40,7 @@ public class RecoveryKeyCreationController implements FxController {
public NiceSecurePasswordField passwordField; public NiceSecurePasswordField passwordField;
@Inject @Inject
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) { public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) {
this.window = window; this.window = window;
this.successScene = successScene; this.successScene = successScene;
this.vault = vault; this.vault = vault;
@@ -1,26 +1,73 @@
package org.cryptomator.ui.recoverykey; package org.cryptomator.ui.recoverykey;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.print.PageLayout;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.text.Font;
import javafx.scene.text.FontSmoothingType;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
@RecoveryKeyScoped
public class RecoveryKeyDisplayController implements FxController { public class RecoveryKeyDisplayController implements FxController {
private final Stage window; private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
private final Vault vault;
private final StringProperty recoveryKeyProperty;
@Inject private final Stage window;
public RecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) { private final String vaultName;
private final String recoveryKey;
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
this.window = window; this.window = window;
this.vault = vault; this.vaultName = vaultName;
this.recoveryKeyProperty = recoveryKey; this.recoveryKey = recoveryKey;
}
@FXML
public void printRecoveryKey() {
// TODO localize
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(window)) {
PageLayout pageLayout = job.getJobSettings().getPageLayout();
Text heading = new Text("Cryptomator Recovery Key\n" + vaultName + "\n");
heading.setFont(Font.font("serif", FontWeight.BOLD, 20));
heading.setFontSmoothingType(FontSmoothingType.LCD);
Text key = new Text(recoveryKey);
key.setFont(Font.font("serif", FontWeight.NORMAL, 16));
key.setFontSmoothingType(FontSmoothingType.GRAY);
TextFlow textFlow = new TextFlow();
textFlow.setPrefSize(pageLayout.getPrintableWidth(), pageLayout.getPrintableHeight());
textFlow.getChildren().addAll(heading, key);
textFlow.setLineSpacing(6);
if (job.printPage(textFlow)) {
LOG.info("Recovery key printed.");
job.endJob();
} else {
LOG.warn("Printing recovery key failed.");
}
} else {
LOG.info("Printing recovery key canceled by user.");
}
}
@FXML
public void copyRecoveryKey() {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(recoveryKey);
Clipboard.getSystemClipboard().setContent(clipboardContent);
LOG.info("Recovery key copied to clipboard.");
} }
@FXML @FXML
@@ -30,15 +77,15 @@ public class RecoveryKeyDisplayController implements FxController {
/* Getter/Setter */ /* Getter/Setter */
public Vault getVault() { public boolean isPrinterSupported() {
return vault; return Printer.getDefaultPrinter() != null;
}
public ReadOnlyStringProperty recoveryKeyProperty() {
return recoveryKeyProperty;
} }
public String getRecoveryKey() { public String getRecoveryKey() {
return recoveryKeyProperty.get(); return recoveryKey;
}
public String getVaultName() {
return vaultName;
} }
} }
@@ -10,6 +10,7 @@ import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
@@ -63,10 +64,10 @@ abstract class RecoveryKeyModule {
} }
@Provides @Provides
@FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
@RecoveryKeyScoped @RecoveryKeyScoped
static Scene provideRecoveryKeyDisplayScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) { static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/recoverykey_display.fxml"); return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
} }
// ------------------ // ------------------
@@ -76,9 +77,16 @@ abstract class RecoveryKeyModule {
@FxControllerKey(RecoveryKeyCreationController.class) @FxControllerKey(RecoveryKeyCreationController.class)
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller); abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
@Binds @Provides
@IntoMap @IntoMap
@FxControllerKey(RecoveryKeyDisplayController.class) @FxControllerKey(RecoveryKeyDisplayController.class)
abstract FxController bindRecoveryKeyDisplayController(RecoveryKeyDisplayController controller); static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get());
}
@Binds
@IntoMap
@FxControllerKey(RecoveryKeySuccessController.class)
abstract FxController bindRecoveryKeySuccessController(RecoveryKeySuccessController controller);
} }
@@ -0,0 +1,24 @@
package org.cryptomator.ui.recoverykey;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
@RecoveryKeyScoped
public class RecoveryKeySuccessController implements FxController {
private final Stage window;
@Inject
public RecoveryKeySuccessController(@RecoveryKeyWindow Stage window) {
this.window = window;
}
@FXML
public void close() {
window.close();
}
}
@@ -3,11 +3,14 @@ package org.cryptomator.ui.traymenu;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState; import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.launcher.FxApplicationStarter; import org.cryptomator.ui.launcher.FxApplicationStarter;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -37,17 +40,17 @@ class TrayMenuController {
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final FxApplicationStarter fxApplicationStarter; private final FxApplicationStarter fxApplicationStarter;
private final CountDownLatch shutdownLatch; private final CountDownLatch shutdownLatch;
private final Settings settings; private final ShutdownHook shutdownHook;
private final ObservableList<Vault> vaults; private final ObservableList<Vault> vaults;
private final PopupMenu menu; private final PopupMenu menu;
private final AtomicBoolean allowSuddenTermination; private final AtomicBoolean allowSuddenTermination;
@Inject @Inject
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, Settings settings, ObservableList<Vault> vaults) { TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
this.fxApplicationStarter = fxApplicationStarter; this.fxApplicationStarter = fxApplicationStarter;
this.shutdownLatch = shutdownLatch; this.shutdownLatch = shutdownLatch;
this.settings = settings; this.shutdownHook = shutdownHook;
this.vaults = vaults; this.vaults = vaults;
this.menu = new PopupMenu(); this.menu = new PopupMenu();
this.allowSuddenTermination = new AtomicBoolean(true); this.allowSuddenTermination = new AtomicBoolean(true);
@@ -71,6 +74,7 @@ class TrayMenuController {
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) { if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest); Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
} }
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
// allow sudden termination // allow sudden termination
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) { if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
@@ -112,6 +116,11 @@ class TrayMenuController {
} }
menu.addSeparator(); menu.addSeparator();
MenuItem lockAllItem = new MenuItem(resourceBundle.getString("traymenu.lockAllVaults"));
lockAllItem.addActionListener(this::lockAllVaults);
lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty());
menu.add(lockAllItem);
MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication")); MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication"));
quitApplicationItem.addActionListener(this::quitApplication); quitApplicationItem.addActionListener(this::quitApplication);
menu.add(quitApplicationItem); menu.add(quitApplicationItem);
@@ -126,11 +135,11 @@ class TrayMenuController {
submenu.add(unlockItem); submenu.add(unlockItem);
} else if (vault.isUnlocked()) { } else if (vault.isUnlocked()) {
MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock")); MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock"));
lockItem.setEnabled(false); // TODO add action listener lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault));
submenu.add(lockItem); submenu.add(lockItem);
MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal")); MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal"));
revealItem.setEnabled(false); // TODO add action listener revealItem.addActionListener(createActionListenerForVault(vault, this::revealVault));
submenu.add(revealItem); submenu.add(revealItem);
} }
@@ -145,12 +154,24 @@ class TrayMenuController {
fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault)); fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault));
} }
private void lockVault(Vault vault) {
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lock(vault, false));
}
private void lockAllVaults(ActionEvent actionEvent) {
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
}
private void revealVault(Vault vault) {
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().reveal(vault));
}
void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) { void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow()); fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
} }
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) { private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
fxApplicationStarter.get(true).thenAccept(FxApplication::showPreferencesWindow); fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
} }
private void handleQuitRequest(EventObject e, QuitResponse response) { private void handleQuitRequest(EventObject e, QuitResponse response) {
@@ -174,4 +195,16 @@ class TrayMenuController {
} }
}); });
} }
private void forceUnmountRemainingVaults() {
for (Vault vault : vaults) {
if (vault.isUnlocked()) {
try {
vault.lock(true);
} catch (Volume.VolumeException e) {
LOG.error("Failed to unmount vault " + vault.getPath(), e);
}
}
}
}
} }
@@ -7,6 +7,7 @@ import javafx.animation.Timeline;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.WritableValue; import javafx.beans.value.WritableValue;
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.NotDirectoryException; import java.nio.file.NotDirectoryException;
import java.util.Arrays; import java.util.Arrays;
@@ -49,19 +51,25 @@ public class UnlockController implements FxController {
private final ObjectBinding<ContentDisplay> unlockButtonState; private final ObjectBinding<ContentDisplay> unlockButtonState;
private final Optional<KeychainAccess> keychainAccess; private final Optional<KeychainAccess> keychainAccess;
private final Lazy<Scene> successScene; private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final Lazy<Scene> genericErrorScene;
private final ObjectProperty<Exception> genericErrorCause;
private final ForgetPasswordComponent.Builder forgetPassword; private final ForgetPasswordComponent.Builder forgetPassword;
private final BooleanProperty unlockButtonDisabled; private final BooleanProperty unlockButtonDisabled;
public NiceSecurePasswordField passwordField; public NiceSecurePasswordField passwordField;
public CheckBox savePassword; public CheckBox savePassword;
@Inject @Inject
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, ForgetPasswordComponent.Builder forgetPassword) { public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Exception> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.executor = executor; this.executor = executor;
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty()); this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
this.keychainAccess = keychainAccess; this.keychainAccess = keychainAccess;
this.successScene = successScene; this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.genericErrorScene = genericErrorScene;
this.genericErrorCause = genericErrorCause;
this.forgetPassword = forgetPassword; this.forgetPassword = forgetPassword;
this.unlockButtonDisabled = new SimpleBooleanProperty(); this.unlockButtonDisabled = new SimpleBooleanProperty();
} }
@@ -100,17 +108,16 @@ public class UnlockController implements FxController {
shakeWindow(); shakeWindow();
passwordField.selectAll(); passwordField.selectAll();
passwordField.requestFocus(); passwordField.requestFocus();
}).onError(UnsupportedVaultFormatException.class, e -> {
// TODO
}).onError(NotDirectoryException.class, e -> { }).onError(NotDirectoryException.class, e -> {
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage()); LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
// TODO window.setScene(invalidMountPointScene.get());
}).onError(DirectoryNotEmptyException.class, e -> { }).onError(DirectoryNotEmptyException.class, e -> {
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage()); LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
// TODO window.setScene(invalidMountPointScene.get());
}).onError(Exception.class, e -> { // including RuntimeExceptions }).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Unlock failed for technical reasons.", e); LOG.error("Unlock failed for technical reasons.", e);
// TODO genericErrorCause.set(e);
window.setScene(genericErrorScene.get());
}).andFinally(() -> { }).andFinally(() -> {
if (!vault.isUnlocked()) { if (!vault.isUnlocked()) {
vault.setState(VaultState.LOCKED); vault.setState(VaultState.LOCKED);
@@ -0,0 +1,29 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
@UnlockScoped
public class UnlockGenericErrorController implements FxController {
private final Stage window;
private final Lazy<Scene> unlockScene;
@Inject
UnlockGenericErrorController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene) {
this.window = window;
this.unlockScene = unlockScene;
}
@FXML
public void back() {
window.setScene(unlockScene.get());
}
}
@@ -0,0 +1,39 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
@UnlockScoped
public class UnlockInvalidMountPointController implements FxController {
private final Stage window;
private final Lazy<Scene> unlockScene;
private final Vault vault;
@Inject
UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @UnlockWindow Vault vault) {
this.window = window;
this.unlockScene = unlockScene;
this.vault = vault;
}
@FXML
public void back() {
window.setScene(unlockScene.get());
}
/* Getter/Setter */
public String getMountPoint() {
return vault.getVaultSettings().getIndividualMountPath().orElse("AUTO");
}
}
@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Modality; import javafx.stage.Modality;
@@ -14,6 +16,7 @@ import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StackTraceController;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import javax.inject.Named; import javax.inject.Named;
@@ -44,6 +47,13 @@ abstract class UnlockModule {
return stage; return stage;
} }
@Provides
@Named("genericErrorCause")
@UnlockScoped
static ObjectProperty<Exception> provideGenericErrorCause() {
return new SimpleObjectProperty<>();
}
@Provides @Provides
@FxmlScene(FxmlFile.UNLOCK) @FxmlScene(FxmlFile.UNLOCK)
@UnlockScoped @UnlockScoped
@@ -58,6 +68,21 @@ abstract class UnlockModule {
return fxmlLoaders.createScene("/fxml/unlock_success.fxml"); return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
} }
@Provides
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
@UnlockScoped
static Scene provideInvalidMountPointScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/unlock_invalid_mount_point.fxml");
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR)
@UnlockScoped
static Scene provideGenericErrorScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/unlock_generic_error.fxml");
}
// ------------------ // ------------------
@@ -71,5 +96,22 @@ abstract class UnlockModule {
@FxControllerKey(UnlockSuccessController.class) @FxControllerKey(UnlockSuccessController.class)
abstract FxController bindUnlockSuccessController(UnlockSuccessController controller); abstract FxController bindUnlockSuccessController(UnlockSuccessController controller);
@Binds
@IntoMap
@FxControllerKey(UnlockInvalidMountPointController.class)
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
@Binds
@IntoMap
@FxControllerKey(UnlockGenericErrorController.class)
abstract FxController bindUnlockGenericErrorController(UnlockGenericErrorController controller);
@Provides
@IntoMap
@FxControllerKey(StackTraceController.class)
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Exception> errorCause) {
return new StackTraceController(errorCause.get());
}
} }
@@ -5,13 +5,13 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.common.VaultService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -26,14 +26,16 @@ public class UnlockSuccessController implements FxController {
private final Stage window; private final Stage window;
private final Vault vault; private final Vault vault;
private final ExecutorService executor; private final ExecutorService executor;
private final VaultService vaultService;
private final ObjectProperty<ContentDisplay> revealButtonState; private final ObjectProperty<ContentDisplay> revealButtonState;
private final BooleanProperty revealButtonDisabled; private final BooleanProperty revealButtonDisabled;
@Inject @Inject
public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor) { public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, VaultService vaultService) {
this.window = window; this.window = window;
this.vault = vault; this.vault = vault;
this.executor = executor; this.executor = executor;
this.vaultService = vaultService;
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY); this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
this.revealButtonDisabled = new SimpleBooleanProperty(); this.revealButtonDisabled = new SimpleBooleanProperty();
} }
@@ -49,17 +51,19 @@ public class UnlockSuccessController implements FxController {
LOG.trace("UnlockSuccessController.revealAndClose()"); LOG.trace("UnlockSuccessController.revealAndClose()");
revealButtonState.set(ContentDisplay.LEFT); revealButtonState.set(ContentDisplay.LEFT);
revealButtonDisabled.set(true); revealButtonDisabled.set(true);
Tasks.create(() -> {
vault.reveal(); Task<Vault> revealTask = vaultService.createRevealTask(vault);
}).onSuccess(() -> { revealTask.setOnSucceeded(evt -> {
window.close();
}).onError(InvalidPassphraseException.class, e -> {
// TODO
LOG.warn("Reveal failed.", e);
}).andFinally(() -> {
revealButtonState.set(ContentDisplay.TEXT_ONLY); revealButtonState.set(ContentDisplay.TEXT_ONLY);
revealButtonDisabled.set(false); revealButtonDisabled.set(false);
}).runOnce(executor); window.close();
});
revealTask.setOnFailed(evt -> {
LOG.warn("Reveal failed.", revealTask.getException());
revealButtonState.set(ContentDisplay.TEXT_ONLY);
revealButtonDisabled.set(false);
});
executor.execute(revealTask);
} }
/* Getter/Setter */ /* Getter/Setter */
@@ -1,34 +1,76 @@
package org.cryptomator.ui.vaultoptions; package org.cryptomator.ui.vaultoptions;
import com.google.common.base.Strings;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.WindowsDriveLetters;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File;
import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.Set;
/**
* TODO: if WebDav is selected on a windows system, custom mount directory is _not_ supported. This is currently not indicated/shown/etc in the ui
*/
@VaultOptionsScoped @VaultOptionsScoped
public class MountOptionsController implements FxController { public class MountOptionsController implements FxController {
private final Stage window;
private final Vault vault; private final Vault vault;
private final BooleanProperty osIsWindows = new SimpleBooleanProperty(SystemUtils.IS_OS_WINDOWS);
private final BooleanBinding webDavAndWindows;
private final WindowsDriveLetters windowsDriveLetters;
private final ResourceBundle resourceBundle;
public TextField driveName; public TextField driveName;
public CheckBox readOnlyCheckbox; public CheckBox readOnlyCheckbox;
public CheckBox customMountFlagsCheckbox; public CheckBox customMountFlagsCheckbox;
public TextField mountFlags; public TextField mountFlags;
public ToggleGroup mountPoint;
public RadioButton mountPointAuto;
public RadioButton mountPointWinDriveLetter;
public RadioButton mountPointCustomDir;
public ChoiceBox<String> driveLetterSelection;
@Inject @Inject
MountOptionsController(@VaultOptionsWindow Vault vault) { MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault; this.vault = vault;
this.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows);
this.windowsDriveLetters = windowsDriveLetters;
this.resourceBundle = resourceBundle;
} }
@FXML @FXML
public void initialize() { public void initialize() {
driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName()); driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName());
// readonly:
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty()); readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty());
// custom mount flags:
mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags()); customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
if (vault.isHavingCustomMountFlags()) { if (vault.isHavingCustomMountFlags()) {
mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags()); mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
@@ -36,6 +78,27 @@ public class MountOptionsController implements FxController {
} else { } else {
mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); mountFlags.textProperty().bind(vault.defaultMountFlagsProperty());
} }
// mount point options:
mountPoint.selectedToggleProperty().addListener(this::toggleMountPoint);
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters));
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
if (vault.getVaultSettings().usesIndividualMountPath().get()) {
mountPoint.selectToggle(mountPointCustomDir);
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
mountPoint.selectToggle(mountPointWinDriveLetter);
} else {
mountPoint.selectToggle(mountPointAuto);
}
vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
vault.getVaultSettings().winDriveLetter().bind( //
Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
.then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
.otherwise((String) null) //
);
} }
@FXML @FXML
@@ -51,4 +114,81 @@ public class MountOptionsController implements FxController {
mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); mountFlags.textProperty().bind(vault.defaultMountFlagsProperty());
} }
} }
@FXML
private void chooseCustomMountPoint() {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
try {
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
} catch (Exception e) {
//NO-OP
}
File file = directoryChooser.showDialog(window);
if (file != null) {
vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath());
} else {
vault.getVaultSettings().individualMountPath().set(null);
}
}
private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().individualMountPath().get())) {
chooseCustomMountPoint();
}
}
/**
* Converts 'C' to "C:" to translate between model and GUI.
*/
private static class WinDriveLetterLabelConverter extends StringConverter<String> {
private final Set<String> occupiedDriveLetters;
WinDriveLetterLabelConverter(WindowsDriveLetters windowsDriveLetters) {
this.occupiedDriveLetters = windowsDriveLetters.getOccupiedDriveLetters();
}
@Override
public String toString(String driveLetter) {
if (occupiedDriveLetters.contains(driveLetter)) {
return driveLetter + ": (occupied)"; // TODO localize?
} else {
return driveLetter + ":";
}
}
@Override
public String fromString(String string) {
throw new UnsupportedOperationException();
}
}
// Getter & Setter
public BooleanProperty osIsWindowsProperty() {
return osIsWindows;
}
public boolean getOsIsWindows() {
return osIsWindows.get();
}
public BooleanBinding webDavAndWindowsProperty() {
return webDavAndWindows;
}
public boolean isWebDavAndWindows() {
return webDavAndWindows.get();
}
public StringProperty customMountPathProperty() {
return vault.getVaultSettings().individualMountPath();
}
public String getCustomMountPath() {
return vault.getVaultSettings().individualMountPath().get();
}
} }
+49 -31
View File
@@ -78,6 +78,7 @@
SCROLL_BAR_THUMB_NORMAL: GRAY_3; SCROLL_BAR_THUMB_NORMAL: GRAY_3;
SCROLL_BAR_THUMB_HOVER: GRAY_4; SCROLL_BAR_THUMB_HOVER: GRAY_4;
INDICATOR_BG: RED_5; INDICATOR_BG: RED_5;
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
PROGRESS_INDICATOR_BEGIN: GRAY_7; PROGRESS_INDICATOR_BEGIN: GRAY_7;
PROGRESS_INDICATOR_END: GRAY_5; PROGRESS_INDICATOR_END: GRAY_5;
PROGRESS_BAR_BG: GRAY_2; PROGRESS_BAR_BG: GRAY_2;
@@ -116,6 +117,10 @@
-fx-font-size: 0.8em; -fx-font-size: 0.8em;
} }
.label-extra-small {
-fx-font-size: 0.64em;
}
.text-flow > * { .text-flow > * {
-fx-fill: TEXT_FILL; -fx-fill: TEXT_FILL;
} }
@@ -179,20 +184,6 @@
-fx-fill: CONTROL_WHITE_BG_ARMED; -fx-fill: CONTROL_WHITE_BG_ARMED;
} }
.main-window .resizer {
-fx-background-color: linear-gradient(to bottom right,
transparent 50%,
CONTROL_BORDER_NORMAL 51%,
CONTROL_BORDER_NORMAL 60%,
transparent 61%,
transparent 70%,
CONTROL_BORDER_NORMAL 71%,
CONTROL_BORDER_NORMAL 80%,
transparent 81%
);
-fx-cursor: nw_resize;
}
.main-window .update-indicator { .main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG; -fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px; -fx-background-insets: 0, 1px, 2px;
@@ -201,6 +192,16 @@
-fx-translate-y: 1px; -fx-translate-y: 1px;
} }
.main-window .drag-n-drop-indicator {
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
-fx-padding: 3px;
}
/******************************************************************************* /*******************************************************************************
* * * *
* TabPane * * TabPane *
@@ -212,34 +213,36 @@
} }
.tab-pane > .tab-header-area { .tab-pane > .tab-header-area {
-fx-padding: 6px 12px 0 12px; -fx-padding: 0 12px;
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 0 0 1px 0; -fx-background-insets: 0, 0 0 3px 0;
} }
.tab-pane > .tab-header-area > .headers-region > .tab { .tab-pane .tab {
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0 0 1px 0, 1px; -fx-background-insets: 0, 0 0 3px 0;
-fx-background-radius: 4px 4px 0 0; -fx-padding: 6px 12px;
-fx-padding: 0.2em 1em 0.2em 1em;
} }
.tab-pane > .tab-header-area > .headers-region > .tab:selected { .tab-pane .tab:selected {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-insets: 0, 1px 1px 0 1px;
} }
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label { .tab-pane .tab .tab-label {
-fx-text-fill: TEXT_FILL; -fx-text-fill: SECONDARY_BG;
-fx-alignment: CENTER; -fx-alignment: CENTER;
} }
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon { .tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG; -fx-fill: SECONDARY_BG;
} }
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { .tab-pane .tab:selected .glyph-icon {
-fx-fill: TEXT_FILL; -fx-fill: PRIMARY_BG;
}
.tab-pane .tab:selected .tab-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
} }
/******************************************************************************* /*******************************************************************************
@@ -672,7 +675,6 @@
******************************************************************************/ ******************************************************************************/
.choice-box { .choice-box {
-fx-text-fill: TEXT_FILL;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px; -fx-background-insets: 0, 1px;
-fx-background-radius: 4px; -fx-background-radius: 4px;
@@ -683,6 +685,18 @@
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL; -fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
} }
.choice-box:disabled {
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
.choice-box > .label {
-fx-text-fill: TEXT_FILL;
}
.choice-box:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
}
.choice-box > .open-button { .choice-box > .open-button {
-fx-padding: 0 0 0 0.3em; -fx-padding: 0 0 0 0.3em;
} }
@@ -694,6 +708,10 @@
-fx-shape: "M 0 0 h 7 l -3.5 4 z"; -fx-shape: "M 0 0 h 7 l -3.5 4 z";
} }
.choice-box:disabled > .open-button > .arrow {
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
}
.choice-box .context-menu { .choice-box .context-menu {
-fx-translate-x: -1.4em; -fx-translate-x: -1.4em;
} }
+49 -31
View File
@@ -78,6 +78,7 @@
SCROLL_BAR_THUMB_NORMAL: GRAY_7; SCROLL_BAR_THUMB_NORMAL: GRAY_7;
SCROLL_BAR_THUMB_HOVER: GRAY_6; SCROLL_BAR_THUMB_HOVER: GRAY_6;
INDICATOR_BG: RED_5; INDICATOR_BG: RED_5;
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
PROGRESS_INDICATOR_BEGIN: GRAY_2; PROGRESS_INDICATOR_BEGIN: GRAY_2;
PROGRESS_INDICATOR_END: GRAY_4; PROGRESS_INDICATOR_END: GRAY_4;
PROGRESS_BAR_BG: GRAY_8; PROGRESS_BAR_BG: GRAY_8;
@@ -116,6 +117,10 @@
-fx-font-size: 0.8em; -fx-font-size: 0.8em;
} }
.label-extra-small {
-fx-font-size: 0.64em;
}
.text-flow > * { .text-flow > * {
-fx-fill: TEXT_FILL; -fx-fill: TEXT_FILL;
} }
@@ -179,20 +184,6 @@
-fx-fill: CONTROL_WHITE_BG_ARMED; -fx-fill: CONTROL_WHITE_BG_ARMED;
} }
.main-window .resizer {
-fx-background-color: linear-gradient(to bottom right,
transparent 50%,
CONTROL_BORDER_NORMAL 51%,
CONTROL_BORDER_NORMAL 60%,
transparent 61%,
transparent 70%,
CONTROL_BORDER_NORMAL 71%,
CONTROL_BORDER_NORMAL 80%,
transparent 81%
);
-fx-cursor: nw_resize;
}
.main-window .update-indicator { .main-window .update-indicator {
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG; -fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
-fx-background-insets: 0, 1px, 2px; -fx-background-insets: 0, 1px, 2px;
@@ -201,6 +192,16 @@
-fx-translate-y: 1px; -fx-translate-y: 1px;
} }
.main-window .drag-n-drop-indicator {
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
-fx-border-width: 3px;
}
.main-window .drag-n-drop-indicator .drag-n-drop-header {
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
-fx-padding: 3px;
}
/******************************************************************************* /*******************************************************************************
* * * *
* TabPane * * TabPane *
@@ -212,34 +213,36 @@
} }
.tab-pane > .tab-header-area { .tab-pane > .tab-header-area {
-fx-padding: 6px 12px 0 12px; -fx-padding: 0 12px;
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 0 0 1px 0; -fx-background-insets: 0, 0 0 3px 0;
} }
.tab-pane > .tab-header-area > .headers-region > .tab { .tab-pane .tab {
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0 0 1px 0, 1px; -fx-background-insets: 0, 0 0 3px 0;
-fx-background-radius: 4px 4px 0 0; -fx-padding: 6px 12px;
-fx-padding: 0.2em 1em 0.2em 1em;
} }
.tab-pane > .tab-header-area > .headers-region > .tab:selected { .tab-pane .tab:selected {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
-fx-background-insets: 0, 1px 1px 0 1px;
} }
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label { .tab-pane .tab .tab-label {
-fx-text-fill: TEXT_FILL; -fx-text-fill: SECONDARY_BG;
-fx-alignment: CENTER; -fx-alignment: CENTER;
} }
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon { .tab-pane .tab .glyph-icon {
-fx-fill: SECONDARY_BG; -fx-fill: SECONDARY_BG;
} }
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { .tab-pane .tab:selected .glyph-icon {
-fx-fill: TEXT_FILL; -fx-fill: PRIMARY_BG;
}
.tab-pane .tab:selected .tab-label {
-fx-text-fill: TEXT_FILL_PRIMARY;
} }
/******************************************************************************* /*******************************************************************************
@@ -672,7 +675,6 @@
******************************************************************************/ ******************************************************************************/
.choice-box { .choice-box {
-fx-text-fill: TEXT_FILL;
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL; -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
-fx-background-insets: 0, 1px; -fx-background-insets: 0, 1px;
-fx-background-radius: 4px; -fx-background-radius: 4px;
@@ -683,6 +685,18 @@
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL; -fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
} }
.choice-box:disabled {
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
}
.choice-box > .label {
-fx-text-fill: TEXT_FILL;
}
.choice-box:disabled > .label {
-fx-text-fill: TEXT_FILL_SECONDARY;
}
.choice-box > .open-button { .choice-box > .open-button {
-fx-padding: 0 0 0 0.3em; -fx-padding: 0 0 0 0.3em;
} }
@@ -694,6 +708,10 @@
-fx-shape: "M 0 0 h 7 l -3.5 4 z"; -fx-shape: "M 0 0 h 7 l -3.5 4 z";
} }
.choice-box:disabled > .open-button > .arrow {
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
}
.choice-box .context-menu { .choice-box .context-menu {
-fx-translate-x: -1.4em; -fx-translate-x: -1.4em;
} }
@@ -11,8 +11,8 @@
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController" fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="CENTER"> alignment="CENTER">
<padding> <padding>
@@ -11,9 +11,9 @@
<?import org.cryptomator.ui.controls.FormattedLabel?> <?import org.cryptomator.ui.controls.FormattedLabel?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExisitingController" fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExistingController"
minWidth="400" minWidth="450"
maxWidth="400" maxWidth="450"
minHeight="145" minHeight="145"
spacing="12"> spacing="12">
<padding> <padding>
@@ -14,8 +14,8 @@
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController" fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<fx:define> <fx:define>
@@ -29,8 +29,10 @@
<VBox spacing="6"> <VBox spacing="6">
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/> <Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
<RadioButton fx:id="iclouddriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="iCloud Drive" visible="${controller.locationPresets.foundIclouddrive}" managed="${controller.locationPresets.foundIclouddrive}"/>
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/> <RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/> <RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.locationPresets.foundOnedrive}" managed="${controller.locationPresets.foundOnedrive}"/>
<HBox spacing="12" alignment="CENTER_LEFT"> <HBox spacing="12" alignment="CENTER_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/> <RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}"> <Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
@@ -12,8 +12,8 @@
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultNameController" fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultNameController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<padding> <padding>
@@ -3,42 +3,34 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?> <?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?> <?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultPasswordController" fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultPasswordController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<fx:define>
<ToggleGroup fx:id="recoveryKeyChoice"/>
</fx:define>
<padding> <padding>
<Insets topRightBottomLeft="24"/> <Insets topRightBottomLeft="24"/>
</padding> </padding>
<children> <children>
<fx:include source="/fxml/new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<VBox spacing="6"> <VBox spacing="6">
<Label text="%addvaultwizard.new.enterPassword" labelFor="$passwordField"/> <Label text="%addvaultwizard.new.generateRecoveryKeyChoice" wrapText="true"/>
<NiceSecurePasswordField fx:id="passwordField"/> <RadioButton fx:id="showRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.yes" />
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/> <RadioButton fx:id="skipRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.no" />
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" labelFor="$passwordField" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
</VBox>
<VBox spacing="6">
<Label text="%addvaultwizard.new.reenterPassword" labelFor="$reenterField"/>
<NiceSecurePasswordField fx:id="reenterField"/>
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" labelFor="$reenterField"/>
</HBox>
</VBox> </VBox>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
@@ -3,16 +3,13 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?> <?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultRecoveryKeyController" fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultRecoveryKeyController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="CENTER_LEFT"> alignment="CENTER_LEFT">
<padding> <padding>
@@ -21,17 +18,13 @@
<children> <children>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<Label text="%addvaultwizard.new.recoveryKeyInstruction" wrapText="true"/> <fx:include source="/fxml/recoverykey_display.fxml"/>
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true"/>
<CheckBox fx:id="finalConfirmationCheckbox" text="%addvaultwizard.new.recoveryKeySavedCheckbox" wrapText="true"/>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+X"> <ButtonBar buttonMinWidth="120" buttonOrder="+X">
<buttons> <buttons>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!finalConfirmationCheckbox.selected}"/> <Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true"/>
</buttons> </buttons>
</ButtonBar> </ButtonBar>
</children> </children>
@@ -13,8 +13,8 @@
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultSuccessController" fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultSuccessController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="TOP_CENTER"> alignment="TOP_CENTER">
<padding> <padding>
@@ -36,8 +36,8 @@
<ButtonBar buttonMinWidth="120" buttonOrder="+IU"> <ButtonBar buttonMinWidth="120" buttonOrder="+IU">
<buttons> <buttons>
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" onAction="#close"/> <Button text="%generic.button.done" ButtonBar.buttonData="FINISH" onAction="#close" defaultButton="${!controller.vault.locked}"/>
<Button text="%addvaultwizard.success.unlockNow" ButtonBar.buttonData="OTHER" onAction="#unlockAndClose" defaultButton="true"/> <Button text="%addvaultwizard.success.unlockNow" ButtonBar.buttonData="OTHER" onAction="#unlockAndClose" defaultButton="${controller.vault.locked}" visible="${controller.vault.locked}"/>
</buttons> </buttons>
</ButtonBar> </ButtonBar>
</children> </children>
@@ -10,8 +10,8 @@
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController" fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
prefWidth="400" prefWidth="450"
prefHeight="400" prefHeight="450"
spacing="12" spacing="12"
alignment="TOP_CENTER"> alignment="TOP_CENTER">
<padding> <padding>
@@ -29,21 +29,8 @@
<Region prefHeight="12" VBox.vgrow="NEVER"/> <Region prefHeight="12" VBox.vgrow="NEVER"/>
<VBox spacing="6"> <fx:include source="/fxml/new_password.fxml"/>
<Label labelFor="$newPasswordField" text="%changepassword.enterNewPassword"/>
<NiceSecurePasswordField fx:id="newPasswordField"/>
<PasswordStrengthIndicator prefHeight="6" spacing="6" strength="${controller.passwordStrength}"/>
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
</VBox>
<VBox spacing="6">
<Label labelFor="$reenterPasswordField" text="%changepassword.reenterNewPassword"/>
<NiceSecurePasswordField fx:id="reenterPasswordField"/>
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" labelFor="$reenterPasswordField"/>
</HBox>
</VBox>
<CheckBox fx:id="finalConfirmationCheckbox" text="%changepassword.finalConfirmation" wrapText="true"/> <CheckBox fx:id="finalConfirmationCheckbox" text="%changepassword.finalConfirmation" wrapText="true"/>
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
@@ -9,55 +9,32 @@
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.Pane?> <StackPane xmlns="http://javafx.com/javafx"
<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
xmlns:fx="http://javafx.com/fxml" fx:id="root"
fx:id="root" fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController" styleClass="main-window">
styleClass="main-window"> <VBox>
<HBox styleClass="title" fx:id="titleBar" alignment="CENTER" minHeight="50" maxHeight="50" VBox.vgrow="NEVER" spacing="6"> <fx:include source="/fxml/main_window_title.fxml" VBox.vgrow="NEVER" />
<padding> <StackPane VBox.vgrow="ALWAYS">
<Insets bottom="6" left="12" right="12" top="6"/> <SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
</padding> <fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
<children> <fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
<Label text="Cryptomator"/> </SplitPane>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false"> <VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
<graphic> <HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
<StackPane> <FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
<FontAwesome5IconView glyph="COGS"/> <Label text="%main.dropZone.unknownDragboardContent"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/> </HBox>
</StackPane> <HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
</graphic> <FontAwesome5IconView glyph="CHECK"/>
<tooltip> <Label text="%main.dropZone.dropVault"/>
<Tooltip text="%main.preferencesBtn.tooltip"/> </HBox>
</tooltip> <Region VBox.vgrow="ALWAYS"/>
</Button> </VBox>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false"> </StackPane>
<graphic> </VBox>
<FontAwesome5IconView glyph="TIMES"/> <fx:include source="/fxml/main_window_resize.fxml"/>
</graphic> </StackPane>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>
</tooltip>
</Button>
</children>
</HBox>
<StackPane VBox.vgrow="ALWAYS">
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
<fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane>
<Region styleClass="resizer" StackPane.alignment="BOTTOM_RIGHT" fx:id="resizer" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
<!-- TODO: use css instead of adding a Rectangle: -->
<Pane fx:id="dragAndDropIndicator">
<StackPane.margin>
<Insets topRightBottomLeft="24"/>
</StackPane.margin>
<Rectangle arcHeight="4" arcWidth="4" fill="gainsboro" strokeType="CENTERED" strokeWidth="3" strokeDashArray="20, 20" strokeLineJoin="ROUND" stroke="black" height="${dragAndDropIndicator.height}"
width="${dragAndDropIndicator.width}"/>
</Pane>
</StackPane>
</VBox>
@@ -0,0 +1,17 @@
<?import javafx.scene.Cursor?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Region?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.mainwindow.ResizeController"
nodeOrientation="LEFT_TO_RIGHT"
pickOnBounds="false">
<fx:define>
<Cursor fx:id="nwResize" fx:constant="NW_RESIZE"/>
<Cursor fx:id="neResize" fx:constant="NE_RESIZE"/>
</fx:define>
<Region fx:id="tlResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="trResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="blResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="brResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0"/>
</AnchorPane>
@@ -0,0 +1,55 @@
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<HBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:id="titleBar"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowTitleController"
styleClass="title"
alignment="CENTER"
minHeight="50"
maxHeight="50"
spacing="6">
<padding>
<Insets bottom="6" left="12" right="12" top="6"/>
</padding>
<children>
<Label text="Cryptomator"/>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.donationKeyMissing.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.preferencesBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>
</tooltip>
</Button>
</children>
</HBox>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.common.NewPasswordController"
spacing="6"
alignment="CENTER_LEFT">
<fx:define>
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
</fx:define>
<children>
<Label text="%newPassword.promptText" labelFor="$passwordField"/>
<NiceSecurePasswordField fx:id="passwordField"/>
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
<Region/>
<Label text="%newPassword.reenterPassword" labelFor="$reenterField"/>
<NiceSecurePasswordField fx:id="reenterField"/>
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity" graphicTextGap="6" contentDisplay="LEFT" />
</children>
</VBox>
@@ -7,7 +7,10 @@
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:id="tabPane" fx:id="tabPane"
fx:controller="org.cryptomator.ui.preferences.PreferencesController" fx:controller="org.cryptomator.ui.preferences.PreferencesController"
minWidth="400" minWidth="-Infinity"
maxWidth="-Infinity"
prefWidth="500"
tabMinWidth="60"
tabClosingPolicy="UNAVAILABLE" tabClosingPolicy="UNAVAILABLE"
tabDragPolicy="FIXED"> tabDragPolicy="FIXED">
<tabs> <tabs>
@@ -35,5 +38,13 @@
<fx:include source="/fxml/preferences_updates.fxml"/> <fx:include source="/fxml/preferences_updates.fxml"/>
</content> </content>
</Tab> </Tab>
<Tab fx:id="donationKeyTab" text="%preferences.donationKey">
<graphic>
<FontAwesome5IconView glyph="HEART"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_donationkey.fxml"/>
</content>
</Tab>
</tabs> </tabs>
</TabPane> </TabPane>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.preferences.DonationKeyPreferencesController"
spacing="18">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<StackPane VBox.vgrow="NEVER" prefHeight="60">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="USER_CROWN" glyphSize="24"/>
</StackPane>
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
</StackPane>
<VBox HBox.hgrow="ALWAYS" spacing="6">
<Label text="%preferences.donationKey.noDonationKey" wrapText="true" VBox.vgrow="ALWAYS"/>
<Hyperlink text="%preferences.donationKey.getDonationKey" onAction="#getDonationKey" contentDisplay="LEFT">
<graphic>
<FontAwesome5IconView glyph="LINK"/>
</graphic>
</Hyperlink>
</VBox>
</HBox>
</StackPane>
<TextArea fx:id="donationKeyField" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
</children>
</VBox>
@@ -6,21 +6,34 @@
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.preferences.GeneralPreferencesController" fx:controller="org.cryptomator.ui.preferences.GeneralPreferencesController"
spacing="6"> spacing="6">
<fx:define>
<ToggleGroup fx:id="nodeOrientation"/>
</fx:define>
<padding> <padding>
<Insets topRightBottomLeft="12"/> <Insets topRightBottomLeft="12"/>
</padding> </padding>
<children> <children>
<HBox spacing="6" alignment="CENTER_LEFT"> <HBox spacing="6" alignment="CENTER_LEFT">
<Label text="%preferences.general.theme"/> <Label text="%preferences.general.theme"/>
<ChoiceBox fx:id="themeChoiceBox"/> <ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
</HBox> </HBox>
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden"/> <HBox spacing="6" alignment="CENTER_LEFT">
<Label text="%preferences.general.interfaceOrientation" HBox.hgrow="NEVER"/>
<RadioButton fx:id="nodeOrientationLtr" text="%preferences.general.interfaceOrientation.ltr" alignment="CENTER_LEFT" toggleGroup="${nodeOrientation}"/>
<RadioButton fx:id="nodeOrientationRtl" text="%preferences.general.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
</HBox>
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/> <CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>
<CheckBox fx:id="autoStartCheckbox" text="%preferences.general.autoStart" visible="${controller.autoStartSupported}" managed="${controller.autoStartSupported}" onAction="#toggleAutoStart"/>
</children> </children>
</VBox> </VBox>
@@ -1,46 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?> <?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?> <?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?> <?import org.cryptomator.ui.controls.FormattedLabel?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController" fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController"
minWidth="400" minWidth="350"
maxWidth="400" minHeight="280"
minHeight="145"
maxHeight="180"
spacing="12" spacing="12"
alignment="TOP_CENTER"> alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children> <children>
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS"> <FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vaultName}" wrapText="true"/>
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS"> <TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" prefRowCount="4" fx:id="textarea"/>
<FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vault.displayableName}" wrapText="true" VBox.vgrow="NEVER"/> <ButtonBar buttonMinWidth="120" buttonOrder="+R">
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" VBox.vgrow="ALWAYS"/> <buttons>
</VBox> <Button text="%generic.button.print" ButtonBar.buttonData="RIGHT" onAction="#printRecoveryKey" visible="${controller.printerSupported}">
</HBox> <graphic>
<FontAwesome5IconView glyph="PRINT"/>
</graphic>
</Button>
<Button text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
<graphic>
<FontAwesome5IconView glyph="COPY"/>
</graphic>
</Button>
</buttons>
</ButtonBar>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS"> <Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons> <Label text="%recoveryKey.display.StorageHints" wrapText="true"/>
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children> </children>
</VBox> </VBox>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeySuccessController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/recoverykey_display.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.common.StackTraceController"
minWidth="300"
spacing="12">
<children>
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<Label text="%generic.error.title" wrapText="true"/>
<Label text="%generic.error.instruction" wrapText="true"/>
</VBox>
</HBox>
<TextArea text="${controller.stackTrace}" prefRowCount="5" editable="false"/>
</children>
</VBox>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.unlock.UnlockGenericErrorController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/stacktrace.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.unlock.UnlockInvalidMountPointController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel format="%unlock.error.invalidMountPoint" arg1="${controller.mountPoint}" wrapText="true"/>
</VBox>
</HBox>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -2,6 +2,7 @@
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Label?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailNeedsMigrationController" fx:controller="org.cryptomator.ui.mainwindow.VaultDetailNeedsMigrationController"
@@ -11,7 +12,8 @@
<Insets topRightBottomLeft="24"/> <Insets topRightBottomLeft="24"/>
</padding> </padding>
<children> <children>
<Button styleClass="button-large" text="TODO Upgrade Vault" minWidth="120" onAction="#showVaultMigrator"> <Label text="%main.vaultDetail.migratePrompt" wrapText="true"/>
<Button styleClass="button-large" text="%main.vaultDetail.migrateButton" minWidth="120" onAction="#showVaultMigrator">
<graphic> <graphic>
<FontAwesome5IconView glyph="FILE_IMPORT" glyphSize="15"/> <FontAwesome5IconView glyph="FILE_IMPORT" glyphSize="15"/>
</graphic> </graphic>
@@ -1,10 +1,10 @@
<?import javafx.scene.layout.HBox?> <?import javafx.scene.control.Button?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.control.Button?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.ThrougputLabel?> <?import org.cryptomator.ui.controls.ThrougputLabel?>
<VBox xmlns="http://javafx.com/javafx" <VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
@@ -18,7 +18,7 @@
<FontAwesome5IconView glyph="HDD" glyphSize="24"/> <FontAwesome5IconView glyph="HDD" glyphSize="24"/>
<VBox spacing="4" alignment="CENTER_LEFT"> <VBox spacing="4" alignment="CENTER_LEFT">
<Label text="%main.vaultDetail.revealBtn"/> <Label text="%main.vaultDetail.revealBtn"/>
<Label styleClass="label-small" text="${controller.vault.accessPoint}" textOverrun="CENTER_ELLIPSIS"/> <Label styleClass="label-extra-small" text="${controller.vault.accessPoint}" textOverrun="CENTER_ELLIPSIS"/>
</VBox> </VBox>
</HBox> </HBox>
</graphic> </graphic>

Some files were not shown because too many files have changed in this diff Show More