mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-24 00:44:21 +00:00
Compare commits
302
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e680e32d | ||
|
|
d60a024c63 | ||
|
|
cc1b536656 | ||
|
|
85e773abef | ||
|
|
4690bc56aa | ||
|
|
292c2676fd | ||
|
|
f7c6604c7c | ||
|
|
22310eb957 | ||
|
|
6d17d1298b | ||
|
|
e90a680620 | ||
|
|
35d0ccfe89 | ||
|
|
62cc454367 | ||
|
|
8bb277fe56 | ||
|
|
67485b76af | ||
|
|
270e60d7cf | ||
|
|
11ffec862e | ||
|
|
d37e0dd5e1 | ||
|
|
960c3665e0 | ||
|
|
3396c79c38 | ||
|
|
cc1517f1b8 | ||
|
|
4f3ff55ae7 | ||
|
|
f51362e95e | ||
|
|
415dfece58 | ||
|
|
3bdb5353da | ||
|
|
6ddf1b59ef | ||
|
|
ef88f11c4f | ||
|
|
ec800c5439 | ||
|
|
a2f3f5d254 | ||
|
|
f475f70adf | ||
|
|
93c3da66da | ||
|
|
9b6145ce3f | ||
|
|
148eed172a | ||
|
|
c97f146964 | ||
|
|
a1034f5663 | ||
|
|
938b351f33 | ||
|
|
d870ecbcdb | ||
|
|
5dff2126c4 | ||
|
|
41a6cc15e8 | ||
|
|
f21b30c009 | ||
|
|
d69b63acc3 | ||
|
|
f95b2baad5 | ||
|
|
1a4d1fffb3 | ||
|
|
ac536ba125 | ||
|
|
362b225d66 | ||
|
|
7d0bdc1a63 | ||
|
|
94e3b21e44 | ||
|
|
23f89c1dc9 | ||
|
|
ce9c2f2c7a | ||
|
|
44e97ab046 | ||
|
|
5d0a1fd49f | ||
|
|
ffc1d8dc1e | ||
|
|
54fca93bba | ||
|
|
c281687910 | ||
|
|
6229d7abbe | ||
|
|
01644eddb6 | ||
|
|
6e30df3796 | ||
|
|
f87fa319ff | ||
|
|
9b019726bb | ||
|
|
cceafb76ed | ||
|
|
39e2994c69 | ||
|
|
1ddfcc3219 | ||
|
|
687f11596e | ||
|
|
b445f614fb | ||
|
|
c6fc1d93a0 | ||
|
|
28d58922e3 | ||
|
|
b884ea7ddc | ||
|
|
1717e20b61 | ||
|
|
6222f96b01 | ||
|
|
88f83c5cdb | ||
|
|
c8059bef78 | ||
|
|
638b731cf8 | ||
|
|
b2b6e304ed | ||
|
|
0748709f40 | ||
|
|
d8ee446d37 | ||
|
|
9932185ccc | ||
|
|
77afd4688a | ||
|
|
9a5ef3f6ff | ||
|
|
c99f23a4c7 | ||
|
|
274c3886f5 | ||
|
|
95db29158c | ||
|
|
872680a737 | ||
|
|
0ba12f6301 | ||
|
|
248df9da51 | ||
|
|
702408d488 | ||
|
|
dd1506f17a | ||
|
|
7fb5c741ad | ||
|
|
aa61ab2b6e | ||
|
|
7a1e20d732 | ||
|
|
c1a5e187b6 | ||
|
|
57d3f788e6 | ||
|
|
ca73c3ad90 | ||
|
|
aec367dcc7 | ||
|
|
56e7c13cb1 | ||
|
|
3b4f384bfd | ||
|
|
d1a20da7e0 | ||
|
|
4b3533f717 | ||
|
|
6535adef44 | ||
|
|
f69cde1469 | ||
|
|
59643b762f | ||
|
|
ec69c1411b | ||
|
|
ad28d4510d | ||
|
|
111e500928 | ||
|
|
b5cb129ff0 | ||
|
|
8a1586e5e8 | ||
|
|
e1959211de | ||
|
|
53494fa141 | ||
|
|
7fdab3a2ab | ||
|
|
cb749c2fba | ||
|
|
453efe9998 | ||
|
|
2948b78cbe | ||
|
|
5a9f993df8 | ||
|
|
4936cc76d0 | ||
|
|
4a15467ff5 | ||
|
|
bb09b32885 | ||
|
|
4c9372747c | ||
|
|
1c04c22617 | ||
|
|
675146d20c | ||
|
|
56ee6af9de | ||
|
|
926a0b3717 | ||
|
|
eae28ce76d | ||
|
|
99c34539e6 | ||
|
|
0dfafdd874 | ||
|
|
a8cb40831e | ||
|
|
3b1bed9345 | ||
|
|
f18b81bdb7 | ||
|
|
4a61fe372e | ||
|
|
0bd0543d10 | ||
|
|
06a3a04840 | ||
|
|
3eb379b1e9 | ||
|
|
52e7707f81 | ||
|
|
28d2424cd5 | ||
|
|
187e9f17fc | ||
|
|
b9776a1017 | ||
|
|
3fbf28eea9 | ||
|
|
99e7ec7dc2 | ||
|
|
a9b4512ce6 | ||
|
|
feb9ee238d | ||
|
|
748f7ca889 | ||
|
|
87a469f264 | ||
|
|
59e0175d65 | ||
|
|
7595f5317d | ||
|
|
3ba4e87f1d | ||
|
|
0a7aec26cc | ||
|
|
d70b7e12ef | ||
|
|
54d2591391 | ||
|
|
4f70695ceb | ||
|
|
8f0a151018 | ||
|
|
cd64460e62 | ||
|
|
db3c0622c6 | ||
|
|
ac0092b178 | ||
|
|
fab32e9e93 | ||
|
|
3d1129b0f3 | ||
|
|
89b2ff74f3 | ||
|
|
a43e8fc461 | ||
|
|
98d3fddfcc | ||
|
|
ff36fff091 | ||
|
|
7d02108c8b | ||
|
|
593f37d5fe | ||
|
|
7abe9c627b | ||
|
|
a0923c3c9b | ||
|
|
439042ab95 | ||
|
|
113e505c52 | ||
|
|
f01e0ae194 | ||
|
|
5540cef257 | ||
|
|
ab16ee493f | ||
|
|
b1a1d1029c | ||
|
|
a46b4aa768 | ||
|
|
245fe4b525 | ||
|
|
c5a9926652 | ||
|
|
7032862a65 | ||
|
|
2012229c46 | ||
|
|
a6672ddbfc | ||
|
|
8dd2147638 | ||
|
|
7a29a1b680 | ||
|
|
f4983f7862 | ||
|
|
488bd1087a | ||
|
|
d557136295 | ||
|
|
87f4b639a0 | ||
|
|
dbb9379a2e | ||
|
|
fa86ae68ea | ||
|
|
f82fddc8fe | ||
|
|
e6f0b321cb | ||
|
|
41fef58450 | ||
|
|
268026629d | ||
|
|
b95fa1868a | ||
|
|
d2b7376e37 | ||
|
|
8d6eac63e2 | ||
|
|
b8fc2dcb64 | ||
|
|
671f934934 | ||
|
|
58ff6554fc | ||
|
|
cad6b221d9 | ||
|
|
c02d5a9794 | ||
|
|
aac71277e3 | ||
|
|
aa61be7bf5 | ||
|
|
3a4f796c79 | ||
|
|
f070cdc12d | ||
|
|
e14911d4d5 | ||
|
|
aa666ad025 | ||
|
|
cd55c2666f | ||
|
|
0dc6032209 | ||
|
|
f7630c28d6 | ||
|
|
08d9beb6b8 | ||
|
|
5808239416 | ||
|
|
e2f400340b | ||
|
|
5854b24c44 | ||
|
|
7adaf44fb3 | ||
|
|
0bd74056a0 | ||
|
|
bbeea7a2ee | ||
|
|
e3548737f1 | ||
|
|
75d7656824 | ||
|
|
c292f18915 | ||
|
|
9636e7c700 | ||
|
|
eb8f7840cc | ||
|
|
180d79f49e | ||
|
|
29cb1c96b1 | ||
|
|
f61432fc52 | ||
|
|
8e7dbf4640 | ||
|
|
d2528faf3a | ||
|
|
c1a3fe66ef | ||
|
|
ecb4c114a2 | ||
|
|
1ecaf5ae6e | ||
|
|
e88b7f00b3 | ||
|
|
91aaabc7fb | ||
|
|
7414c29593 | ||
|
|
79ae5b7bff | ||
|
|
486a3a07f5 | ||
|
|
02d44eb0e7 | ||
|
|
3aff7bd956 | ||
|
|
cd6a6cc55a | ||
|
|
d0a30a1779 | ||
|
|
a5fb30d7c7 | ||
|
|
ba4e813a35 | ||
|
|
f6d4caee07 | ||
|
|
d3a00e726f | ||
|
|
d71c0695f4 | ||
|
|
394c91c50c | ||
|
|
71c35eaf4c | ||
|
|
70790d0e6a | ||
|
|
8fbc00a417 | ||
|
|
080bf7f540 | ||
|
|
9d988294f3 | ||
|
|
a712013507 | ||
|
|
0aab22975a | ||
|
|
fde6555e4d | ||
|
|
748e76e187 | ||
|
|
55a627f718 | ||
|
|
a19cbff12f | ||
|
|
d5e098a6cd | ||
|
|
3f9fc28e3c | ||
|
|
0bf4081d71 | ||
|
|
de3e392aa3 | ||
|
|
44a5f51c93 | ||
|
|
9c8ea3c2e5 | ||
|
|
4208d1e036 | ||
|
|
154198fd5c | ||
|
|
0ad0d79a54 | ||
|
|
966538e47b | ||
|
|
181a1e7248 | ||
|
|
1a13e03a08 | ||
|
|
0fa0568f24 | ||
|
|
d7e76bee6d | ||
|
|
77dcf4da62 | ||
|
|
6eb0384fa6 | ||
|
|
5af5a6c5b0 | ||
|
|
0e4c833be0 | ||
|
|
a2e4a2c78a | ||
|
|
f4ab2b590f | ||
|
|
80b3d7ab6b | ||
|
|
b23fbd4507 | ||
|
|
5b56ff6d55 | ||
|
|
143b691c61 | ||
|
|
88e4c88a87 | ||
|
|
c9e06ad2d2 | ||
|
|
bd814f729d | ||
|
|
8c8b50b83a | ||
|
|
e9f4790b40 | ||
|
|
ac1149b873 | ||
|
|
51b6f828ed | ||
|
|
b97a99d4e2 | ||
|
|
6ed661aa13 | ||
|
|
f3d46ed767 | ||
|
|
3bde55c4df | ||
|
|
dd591ec258 | ||
|
|
4ce035b554 | ||
|
|
cdc451fdca | ||
|
|
0680d16708 | ||
|
|
1f1792522e | ||
|
|
db53a7a3dc | ||
|
|
9362196d4c | ||
|
|
527d387640 | ||
|
|
5f9dda4bbb | ||
|
|
6a991f3ab5 | ||
|
|
4c023f792c | ||
|
|
6b5f7d37ca | ||
|
|
87158b1e7a | ||
|
|
0b132b7d10 | ||
|
|
cd4cb70896 | ||
|
|
633470b0d6 | ||
|
|
1930090044 | ||
|
|
ccefb3613e | ||
|
|
9e79350b9e | ||
|
|
9092c2325a |
@@ -0,0 +1,5 @@
|
||||
commit_message: "[ci skip]"
|
||||
escape_special_characters: 0
|
||||
files:
|
||||
- source: /main/ui/src/main/resources/i18n/strings.properties
|
||||
translation: /main/ui/src/main/resources/i18n/strings_%two_letters_code%.properties
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # 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
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
+3
-1
@@ -1,12 +1,14 @@
|
||||
# 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
|
||||
daysUntilClose: 30
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- type:security-issue # never close automatically
|
||||
- type:feature-request # never close automatically
|
||||
- state:awaiting-response # handled by different bot
|
||||
- state:blocked
|
||||
- state:confirmed
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
# Label to use when marking an issue as stale
|
||||
|
||||
Generated
+17
-9
@@ -7,21 +7,29 @@
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<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/2.22.1/dagger-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.25.2/dagger-2.25.2.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/guava/guava/25.0-jre/guava-25.0-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.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/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.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/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.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/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$/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.22.1/dagger-spi-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.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/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$/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>
|
||||
<module name="keychain" />
|
||||
<module name="launcher" />
|
||||
|
||||
Generated
+8
-6
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/buildkit" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/ui" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" 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>
|
||||
</project>
|
||||
+11
-10
@@ -1,4 +1,4 @@
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
language: java
|
||||
sudo: false
|
||||
jdk:
|
||||
@@ -17,24 +17,25 @@ addons:
|
||||
- haveged
|
||||
install:
|
||||
- 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:
|
||||
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
|
||||
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/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/launcher/target/site/jacoco/jacoco.xml --partial
|
||||
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
||||
before_deploy:
|
||||
- |
|
||||
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
|
||||
- mvn -fmain/pom.xml package -Prelease -DskipTests
|
||||
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
||||
deploy:
|
||||
- provider: bintray # SNAPSHOTS
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
||||
[](http://twitter.com/Cryptomator)
|
||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
||||
[](https://translate.cryptomator.org/)
|
||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||
[](https://community.cryptomator.org)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
@@ -48,6 +48,12 @@
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -28,13 +28,23 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(subcomponents = {VaultComponent.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
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
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
@@ -56,7 +66,7 @@ public abstract class CommonsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||
Thread t = new Thread(r);
|
||||
@@ -64,7 +74,7 @@ public abstract class CommonsModule {
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
shutdownTaskScheduler.accept(executorService::shutdown);
|
||||
shutdownHook.runOnShutdown(executorService::shutdown);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public class Environment {
|
||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
||||
private static final char PATH_LIST_SEP = ':';
|
||||
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
||||
|
||||
@Inject
|
||||
public Environment() {
|
||||
@@ -37,6 +38,7 @@ public class Environment {
|
||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||
}
|
||||
|
||||
public boolean useCustomLogbackConfig() {
|
||||
@@ -63,6 +65,19 @@ public class Environment {
|
||||
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) {
|
||||
String value = System.getProperty(propertyName);
|
||||
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.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -30,9 +33,11 @@ public class Settings {
|
||||
public static final int DEFAULT_PORT = 42427;
|
||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
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 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 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 ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
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;
|
||||
|
||||
@@ -61,6 +68,8 @@ public class Settings {
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
theme.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
@@ -118,4 +127,12 @@ public class Settings {
|
||||
public ObjectProperty<UiTheme> theme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||
return userInterfaceOrientation;
|
||||
}
|
||||
|
||||
public StringProperty licenseKey() {
|
||||
return licenseKey;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-2
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -36,6 +37,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.name("debugMode").value(value.debugMode().get());
|
||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().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();
|
||||
}
|
||||
|
||||
@@ -85,6 +88,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "theme":
|
||||
settings.theme().set(parseUiTheme(in.nextString()));
|
||||
break;
|
||||
case "uiOrientation":
|
||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
break;
|
||||
case "licenseKey":
|
||||
settings.licenseKey().set(in.nextString());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
@@ -109,7 +118,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
try {
|
||||
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -118,11 +127,20 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
try {
|
||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
List<VaultSettings> result = new ArrayList<>();
|
||||
in.beginArray();
|
||||
|
||||
@@ -69,8 +69,9 @@ public class DokanyVolume implements Volume {
|
||||
} else {
|
||||
//auto assign drive letter
|
||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||
return windowsDriveLetters.getAvailableDriveLetters().iterator().next();
|
||||
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||
} else {
|
||||
//TODO: Error Handling
|
||||
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.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
@@ -67,7 +68,13 @@ public class VaultListManager {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -76,7 +83,7 @@ public class VaultListManager {
|
||||
return comp.vault();
|
||||
}
|
||||
|
||||
private VaultState determineVaultState(Path pathToVault) {
|
||||
public static VaultState determineVaultState(Path pathToVault) {
|
||||
try {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
return VaultState.MISSING;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -14,7 +15,6 @@ import javax.inject.Singleton;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -23,11 +23,11 @@ import java.util.stream.StreamSupport;
|
||||
public final class WindowsDriveLetters {
|
||||
|
||||
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 {
|
||||
try (IntStream stream = IntStream.rangeClosed('D', 'Z')) {
|
||||
D_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet());
|
||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
||||
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,22 @@ public final class WindowsDriveLetters {
|
||||
public WindowsDriveLetters() {
|
||||
}
|
||||
|
||||
public Set<Path> getOccupiedDriveLetters() {
|
||||
public Set<String> getAllDriveLetters() {
|
||||
return A_TO_Z;
|
||||
}
|
||||
|
||||
public Set<String> getOccupiedDriveLetters() {
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
|
||||
return Set.of();
|
||||
} else {
|
||||
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() {
|
||||
Set<Path> occupiedDriveLetters = getOccupiedDriveLetters();
|
||||
Predicate<Path> isOccupiedDriveLetter = occupiedDriveLetters::contains;
|
||||
return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
|
||||
public Set<String> getAvailableDriveLetters() {
|
||||
return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<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 Optional<String> applicationVersion;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final CleanShutdownPerformer shutdownPerformer;
|
||||
private final UiLauncher uiLauncher;
|
||||
|
||||
@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.debugMode = debugMode;
|
||||
this.ipcFactory = ipcFactory;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.shutdownPerformer = shutdownPerformer;
|
||||
this.uiLauncher = uiLauncher;
|
||||
}
|
||||
|
||||
@@ -90,7 +88,6 @@ public class Cryptomator {
|
||||
*/
|
||||
private int runGuiApplication() {
|
||||
try {
|
||||
shutdownPerformer.registerShutdownHook();
|
||||
uiLauncher.launch();
|
||||
shutdownLatch.await();
|
||||
LOG.info("UI shut down");
|
||||
|
||||
@@ -7,18 +7,10 @@ import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
|
||||
return shutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownLatch")
|
||||
|
||||
@@ -5,9 +5,8 @@ import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
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.ShutdownHook;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -16,26 +15,27 @@ import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class LoggerConfiguration {
|
||||
|
||||
private static final double SHUTDOWN_DELAY_MS = 100;
|
||||
|
||||
|
||||
private final LoggerContext context;
|
||||
private final Environment environment;
|
||||
private final Appender<ILoggingEvent> stdout;
|
||||
private final Appender<ILoggingEvent> upgrade;
|
||||
private final Appender<ILoggingEvent> file;
|
||||
private final ShutdownHook shutdownHook;
|
||||
|
||||
@Inject
|
||||
LoggerConfiguration(LoggerContext context, //
|
||||
Environment environment, //
|
||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file) {
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file, //
|
||||
ShutdownHook shutdownHook) {
|
||||
this.context = context;
|
||||
this.environment = environment;
|
||||
this.stdout = stdout;
|
||||
this.upgrade = upgrade;
|
||||
this.file = file;
|
||||
this.shutdownHook = shutdownHook;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
@@ -62,9 +62,7 @@ public class LoggerConfiguration {
|
||||
upgrades.setAdditive(false);
|
||||
|
||||
// add shutdown hook
|
||||
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
|
||||
shutdownHook.setContext(context);
|
||||
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
|
||||
shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-35
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -23,31 +23,28 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependency versions -->
|
||||
<cryptomator.cryptolib.version>1.2.2</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>1.9.0-beta1</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.0</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
||||
|
||||
<javafx.version>12</javafx.version>
|
||||
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<javafx.version>13.0.1</javafx.version>
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
<jwt.version>3.8.3</jwt.version>
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
|
||||
<guava.version>27.1-jre</guava.version>
|
||||
<dagger.version>2.22.1</dagger.version>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
|
||||
<slf4j.version>1.7.26</slf4j.version>
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<dagger.version>2.25.2</dagger.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
<slf4j.version>1.7.29</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<junit.jupiter.version>5.4.2</junit.jupiter.version>
|
||||
<mockito.version>2.27.0</mockito.version>
|
||||
<hamcrest.version>2.1</hamcrest.version>
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||
<mockito.version>3.1.0</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -82,11 +79,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Cryptomator Libs -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>${cryptomator.cryptolib.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
@@ -158,16 +150,18 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
@@ -273,7 +267,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
@@ -302,7 +296,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<version>0.8.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
@@ -329,7 +323,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
<annotationProcessorPaths>
|
||||
@@ -344,7 +338,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
+3
-11
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-alpha1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -22,10 +22,6 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
@@ -41,7 +37,7 @@
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
@@ -54,10 +50,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
@@ -73,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>com.nulab-inc</groupId>
|
||||
<artifactId>zxcvbn</artifactId>
|
||||
<version>1.2.7</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
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;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class AddVaultFailureExistingController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> previousScene;
|
||||
private final StringBinding vaultName;
|
||||
|
||||
@Inject
|
||||
AddVaultFailureExistingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){
|
||||
this.window = window;
|
||||
this.previousScene = previousScene;
|
||||
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close(){
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back(){
|
||||
window.setScene(previousScene.get());
|
||||
}
|
||||
|
||||
// Getter & Setter
|
||||
|
||||
public StringBinding vaultNameProperty(){
|
||||
return vaultName;
|
||||
}
|
||||
|
||||
public String getVaultName(){
|
||||
return vaultName.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
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.recoverykey.RecoveryKeyDisplayController;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -32,30 +30,33 @@ import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
@Module
|
||||
public abstract class AddVaultModule {
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
@Named("newPassword")
|
||||
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||
return new SimpleObjectProperty<>("");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @AddVaultWizardWindow Lazy<Map<KeyCodeCombination, Runnable>> accelerators) {
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.sceneProperty().addListener(observable -> {
|
||||
stage.getScene().getAccelerators().putAll(accelerators.get());
|
||||
});
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
return stage;
|
||||
}
|
||||
@@ -67,6 +68,7 @@ public abstract class AddVaultModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("vaultName")
|
||||
@AddVaultWizardScoped
|
||||
static StringProperty provideVaultName() {
|
||||
return new SimpleStringProperty("");
|
||||
@@ -79,24 +81,11 @@ public abstract class AddVaultModule {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@Named("recoveryKey")
|
||||
@AddVaultWizardScoped
|
||||
static Map<KeyCodeCombination, Runnable> provideDefaultAccellerators(@AddVaultWizardWindow Set<Map.Entry<KeyCombination, Runnable>> accelerators) {
|
||||
return Map.ofEntries(accelerators.toArray(Map.Entry[]::new));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@AddVaultWizardWindow
|
||||
static Map.Entry<KeyCombination, Runnable> provideCloseWindowShortcut(@AddVaultWizardWindow Stage window) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return Map.entry(new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN), window::close);
|
||||
} else {
|
||||
return Map.entry(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), window::close);
|
||||
}
|
||||
static StringProperty provideRecoveryKey() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
@@ -104,48 +93,57 @@ public abstract class AddVaultModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/addvault_welcome.fxml");
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/addvault_existing.fxml");
|
||||
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideChooseExistingVaultErrorScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING_ERROR.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/addvault_new_name.fxml");
|
||||
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/addvault_new_location.fxml");
|
||||
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/addvault_new_password.fxml");
|
||||
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/addvault_success.fxml");
|
||||
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS.getRessourcePathString());
|
||||
}
|
||||
|
||||
// ------------------
|
||||
@@ -160,6 +158,11 @@ public abstract class AddVaultModule {
|
||||
@FxControllerKey(ChooseExistingVaultController.class)
|
||||
abstract FxController bindChooseExistingVaultController(ChooseExistingVaultController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultFailureExistingController.class)
|
||||
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExistingController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultNameController.class)
|
||||
@@ -175,6 +178,25 @@ public abstract class AddVaultModule {
|
||||
@FxControllerKey(CreateNewVaultPasswordController.class)
|
||||
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
|
||||
return new NewPasswordController(resourceBundle, strengthRater, password);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
|
||||
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)
|
||||
|
||||
+8
-7
@@ -28,16 +28,18 @@ public class ChooseExistingVaultController implements FxController {
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> welcomeScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> errorScene;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
@Inject
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR) Lazy<Scene> errorScene, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.successScene = successScene;
|
||||
this.errorScene = errorScene;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vault = vault;
|
||||
this.vaultListManager = vaultListManager;
|
||||
@@ -51,20 +53,19 @@ public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void chooseFileAndNext() {
|
||||
//TODO: error handling & cannot unlock added vault
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if (file != null) {
|
||||
vaultPath.setValue(file.toPath().toAbsolutePath().getParent());
|
||||
File masterkeyFile = fileChooser.showOpenDialog(window);
|
||||
if (masterkeyFile != null) {
|
||||
vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
|
||||
try {
|
||||
Vault newVault = vaultListManager.add(vaultPath.get());
|
||||
vault.set(newVault);
|
||||
window.setScene(successScene.get());
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.error("Nope", e);
|
||||
// TODO
|
||||
LOG.error("Failed to open existing vault.", e);
|
||||
window.setScene(errorScene.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -23,12 +23,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -53,12 +52,14 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||
public ToggleGroup predefinedLocationToggler;
|
||||
public RadioButton iclouddriveRadioButton;
|
||||
public RadioButton dropboxRadioButton;
|
||||
public RadioButton gdriveRadioButton;
|
||||
public RadioButton onedriveRadioButton;
|
||||
public RadioButton customRadioButton;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.chooseNameScene = chooseNameScene;
|
||||
this.choosePasswordScene = choosePasswordScene;
|
||||
@@ -92,10 +93,14 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
}
|
||||
|
||||
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()));
|
||||
} else if (gdriveRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
||||
} else if (onedriveRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
|
||||
} else if (customRadioButton.equals(newValue)) {
|
||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||
}
|
||||
@@ -115,14 +120,12 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
Files.delete(createdDir); // assert: dir exists and is empty
|
||||
window.setScene(choosePasswordScene.get());
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
LOG.warn("Can not use already existing vault path: {}", vaultPath.get());
|
||||
LOG.warn("Can not use already existing vault path {}", vaultPath.get());
|
||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
||||
} catch (NoSuchFileException | DirectoryNotEmptyException e) {
|
||||
LOG.error("Failed to delete recently created directory.", e);
|
||||
// TODO show generic error text for unexpected exception
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Can not create vault at path: {}", vaultPath.get());
|
||||
// TODO show generic error text for unexpected exception
|
||||
LOG.warn("Thrown Exception:", e);
|
||||
warningText.set(resourceBundle.getString("addvaultwizard.new.ioException"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -36,7 +37,7 @@ public class CreateNewVaultNameController implements FxController {
|
||||
private final StringBinding warningText;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.chooseLocationScene = chooseLocationScene;
|
||||
|
||||
+61
-60
@@ -5,17 +5,14 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
@@ -25,14 +22,12 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
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.fxmisc.easybind.EasyBind;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
@@ -56,43 +51,42 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseLocationScene;
|
||||
private final Lazy<Scene> recoveryKeyScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final ExecutorService executor;
|
||||
private final StringProperty vaultName;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final StringProperty vaultNameProperty;
|
||||
private final ObjectProperty<Path> vaultPathProperty;
|
||||
private final ObjectProperty<Vault> vaultProperty;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final ObjectProperty<CharSequence> password;
|
||||
private final ReadmeGenerator readmeGenerator;
|
||||
private final IntegerProperty passwordStrength;
|
||||
private final BooleanProperty processing;
|
||||
private final BooleanProperty readyToCreateVault;
|
||||
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
||||
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public NiceSecurePasswordField reenterField;
|
||||
public Label passwordStrengthLabel;
|
||||
public HBox passwordMatchBox;
|
||||
public FontAwesome5IconView checkmark;
|
||||
public FontAwesome5IconView cross;
|
||||
public Label passwordMatchLabel;
|
||||
public CheckBox finalConfirmationCheckbox;
|
||||
public ToggleGroup recoveryKeyChoice;
|
||||
public Toggle showRecoveryKey;
|
||||
public Toggle skipRecoveryKey;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ExecutorService executor, StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, 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.chooseLocationScene = chooseLocationScene;
|
||||
this.recoveryKeyScene = recoveryKeyScene;
|
||||
this.successScene = successScene;
|
||||
this.executor = executor;
|
||||
this.vaultName = vaultName;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.vaultNameProperty = vaultName;
|
||||
this.vaultPathProperty = vaultPath;
|
||||
this.vaultProperty = vault;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.password = password;
|
||||
this.readmeGenerator = readmeGenerator;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
this.processing = new SimpleBooleanProperty();
|
||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||
@@ -100,22 +94,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty()));
|
||||
//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(finalConfirmationCheckbox.selectedProperty()).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));
|
||||
BooleanBinding isValidNewPassword = Bindings.createBooleanBinding(() -> password.get() != null && password.get().length() > 0, password);
|
||||
readyToCreateVault.bind(isValidNewPassword.and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -125,8 +105,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
Path pathToVault = vaultPath.get();
|
||||
|
||||
Path pathToVault = vaultPathProperty.get();
|
||||
|
||||
try {
|
||||
Files.createDirectory(pathToVault);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
@@ -137,11 +117,41 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
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);
|
||||
Tasks.create(() -> {
|
||||
initializeVault(pathToVault, passwordField.getCharacters());
|
||||
initializeVault(pathToVault, password.get());
|
||||
return recoveryKeyFactory.createRecoveryKey(pathToVault, password.get());
|
||||
}).onSuccess(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 -> {
|
||||
// TODO show generic error screen
|
||||
LOG.error("", e);
|
||||
@@ -170,12 +180,11 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
}
|
||||
LOG.info("Created vault at {}", path);
|
||||
}
|
||||
|
||||
|
||||
private void initializationSucceeded(Path pathToVault) {
|
||||
try {
|
||||
Vault newVault = vaultListManager.add(pathToVault);
|
||||
vault.set(newVault);
|
||||
window.setScene(successScene.get());
|
||||
vaultProperty.set(newVault);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
@@ -184,19 +193,11 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getVaultName() {
|
||||
return vaultName.get();
|
||||
return vaultNameProperty.get();
|
||||
}
|
||||
|
||||
public StringProperty vaultNameProperty() {
|
||||
return vaultName;
|
||||
}
|
||||
|
||||
public IntegerProperty passwordStrengthProperty() {
|
||||
return passwordStrength;
|
||||
}
|
||||
|
||||
public int getPasswordStrength() {
|
||||
return passwordStrength.get();
|
||||
return vaultNameProperty;
|
||||
}
|
||||
|
||||
public BooleanProperty readyToCreateVaultProperty() {
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
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;
|
||||
|
||||
public class CreateNewVaultRecoveryKeyController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene) {
|
||||
this.window = window;
|
||||
this.successScene = successScene;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
}
|
||||
@@ -13,20 +13,30 @@ import java.nio.file.Paths;
|
||||
public class LocationPresets {
|
||||
|
||||
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[] 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> gdriveLocation;
|
||||
private final ReadOnlyObjectProperty<Path> onedriveLocation;
|
||||
private final BooleanBinding foundIclouddrive;
|
||||
private final BooleanBinding foundDropbox;
|
||||
private final BooleanBinding foundGdrive;
|
||||
private final BooleanBinding foundOnedrive;
|
||||
|
||||
@Inject
|
||||
public LocationPresets() {
|
||||
this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
|
||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
||||
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
||||
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
||||
this.foundDropbox = dropboxLocation.isNotNull();
|
||||
this.foundGdrive = gdriveLocation.isNotNull();
|
||||
this.foundOnedrive = onedriveLocation.isNotNull();
|
||||
}
|
||||
|
||||
private static Path existingWritablePath(String... candidates) {
|
||||
@@ -49,6 +59,22 @@ public class LocationPresets {
|
||||
|
||||
/* 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() {
|
||||
return dropboxLocation;
|
||||
}
|
||||
@@ -73,7 +99,7 @@ public class LocationPresets {
|
||||
return gdriveLocation.get();
|
||||
}
|
||||
|
||||
public BooleanBinding froundGdriveProperty() {
|
||||
public BooleanBinding foundGdriveProperty() {
|
||||
return foundGdrive;
|
||||
}
|
||||
|
||||
@@ -81,4 +107,20 @@ public class LocationPresets {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-40
@@ -3,6 +3,7 @@ package org.cryptomator.ui.changepassword;
|
||||
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.Button;
|
||||
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -33,47 +35,24 @@ public class ChangePasswordController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final IntegerProperty passwordStrength;
|
||||
private final ObjectProperty<CharSequence> newPassword;
|
||||
|
||||
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 Button finishButton;
|
||||
|
||||
@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.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
this.newPassword = newPassword;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty()));
|
||||
//binding indicating if the passwords not match
|
||||
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));
|
||||
BooleanBinding hasNotConfirmedCheckbox = finalConfirmationCheckbox.selectedProperty().not();
|
||||
BooleanBinding isInvalidNewPassword = Bindings.createBooleanBinding(() -> newPassword.get() == null || newPassword.get().length() == 0, newPassword);
|
||||
finishButton.disableProperty().bind(hasNotConfirmedCheckbox.or(isInvalidNewPassword));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -84,15 +63,15 @@ public class ChangePasswordController implements FxController {
|
||||
@FXML
|
||||
public void finish() {
|
||||
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());
|
||||
window.close();
|
||||
} catch (IOException e) {
|
||||
//TODO
|
||||
// TODO show generic error screen
|
||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
||||
e.printStackTrace();
|
||||
} catch (InvalidPassphraseException e) {
|
||||
//TODO
|
||||
// TODO shake
|
||||
LOG.info("Wrong old password.");
|
||||
}
|
||||
}
|
||||
@@ -102,12 +81,5 @@ public class ChangePasswordController implements FxController {
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public IntegerProperty passwordStrengthProperty() {
|
||||
return passwordStrength;
|
||||
}
|
||||
|
||||
public int getPasswordStrength() {
|
||||
return passwordStrength.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,30 +4,43 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
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.Provider;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class ChangePasswordModule {
|
||||
|
||||
@Provides
|
||||
@ChangePasswordScoped
|
||||
@Named("newPassword")
|
||||
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||
return new SimpleObjectProperty<>("");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@ChangePasswordScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -57,5 +70,12 @@ abstract class ChangePasswordModule {
|
||||
@IntoMap
|
||||
@FxControllerKey(ChangePasswordController.class)
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.function.Function;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class DefaultSceneFactory implements Function<Parent, Scene> {
|
||||
|
||||
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 final Settings settings;
|
||||
|
||||
@Inject
|
||||
public DefaultSceneFactory(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scene apply(Parent root) {
|
||||
Scene scene = new Scene(root);
|
||||
configureRoot(root);
|
||||
configureScene(scene);
|
||||
return scene;
|
||||
}
|
||||
|
||||
protected void configureRoot(Parent root) {
|
||||
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation());
|
||||
}
|
||||
|
||||
protected void configureScene(Scene scene) {
|
||||
scene.windowProperty().addListener(observable -> {
|
||||
Window window = scene.getWindow();
|
||||
if (window instanceof Stage) {
|
||||
setupDefaultAccelerators(scene, (Stage) window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
scene.getAccelerators().put(ALT_F4, stage::close);
|
||||
} else {
|
||||
scene.getAccelerators().put(SHORTCUT_W, stage::close);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,28 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class FXMLLoaderFactory {
|
||||
|
||||
private final Map<Class<? extends FxController>, Provider<FxController>> factories;
|
||||
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
||||
private final Function<Parent, Scene> sceneFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public FXMLLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
this.factories = factories;
|
||||
public FXMLLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
||||
this.controllerFactories = controllerFactories;
|
||||
this.sceneFactory = sceneFactory;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@@ -34,6 +38,7 @@ public class FXMLLoaderFactory {
|
||||
|
||||
/**
|
||||
* Loads the FXML given fxml resource in a new FXMLLoader instance.
|
||||
*
|
||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||
* @return The FXMLLoader used to load the file
|
||||
* @throws IOException if an error occurs while loading the FXML file
|
||||
@@ -48,6 +53,7 @@ public class FXMLLoaderFactory {
|
||||
|
||||
/**
|
||||
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
||||
*
|
||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
||||
*/
|
||||
@@ -59,14 +65,16 @@ public class FXMLLoaderFactory {
|
||||
throw new UncheckedIOException("Failed to load " + fxmlResourceName, e);
|
||||
}
|
||||
Parent root = loader.getRoot();
|
||||
return new Scene(root);
|
||||
List<String> addtionalStyleSheets = Splitter.on(',').omitEmptyStrings().splitToList(resourceBundle.getString("additionalStyleSheets"));
|
||||
addtionalStyleSheets.forEach(styleSheet -> root.getStylesheets().add("/css/" + styleSheet));
|
||||
return sceneFactory.apply(root);
|
||||
}
|
||||
|
||||
private FxController constructController(Class<?> aClass) {
|
||||
if (!factories.containsKey(aClass)) {
|
||||
if (!controllerFactories.containsKey(aClass)) {
|
||||
throw new IllegalArgumentException("ViewController not registered: " + aClass);
|
||||
} else {
|
||||
return factories.get(aClass).get();
|
||||
return controllerFactories.get(aClass).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package org.cryptomator.ui.common;
|
||||
public enum FxmlFile {
|
||||
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
|
||||
ADDVAULT_EXISTING("/fxml/addvault_existing.fxml"), //
|
||||
ADDVAULT_EXISTING_ERROR("/fxml/addvault_existing_error.fxml"),
|
||||
ADDVAULT_NEW_NAME("/fxml/addvault_new_name.fxml"), //
|
||||
ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), //
|
||||
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
|
||||
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
|
||||
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
|
||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
@@ -15,15 +17,23 @@ public enum FxmlFile {
|
||||
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
|
||||
PREFERENCES("/fxml/preferences.fxml"), //
|
||||
QUIT("/fxml/quit.fxml"), //
|
||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||
UNLOCK("/fxml/unlock2.fxml"), // TODO rename
|
||||
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"), //
|
||||
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
||||
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
|
||||
|
||||
private final String filename;
|
||||
private final String ressourcePathString;
|
||||
|
||||
FxmlFile(String filename) {
|
||||
this.filename = filename;
|
||||
FxmlFile(String ressourcePathString) {
|
||||
this.ressourcePathString = ressourcePathString;
|
||||
}
|
||||
|
||||
public String getRessourcePathString(){
|
||||
return ressourcePathString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -22,31 +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 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 int minPwLength;
|
||||
private final Zxcvbn zxcvbn;
|
||||
|
||||
@Inject
|
||||
public PasswordStrengthUtil(ResourceBundle resourceBundle) {
|
||||
public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.minPwLength = environment.getMinPwLength();
|
||||
this.zxcvbn = new Zxcvbn();
|
||||
this.sanitizedInputs = new ArrayList<>();
|
||||
this.sanitizedInputs.add("cryptomator");
|
||||
}
|
||||
|
||||
public int computeRate(String password) {
|
||||
if (Strings.isNullOrEmpty(password)) {
|
||||
public boolean fulfillsMinimumRequirements(CharSequence password) {
|
||||
return password.length() >= minPwLength;
|
||||
}
|
||||
|
||||
public int computeRate(CharSequence password) {
|
||||
if (password == null || password.length() < minPwLength) {
|
||||
return -1;
|
||||
} else {
|
||||
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) {
|
||||
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
||||
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());
|
||||
if (score.intValue() == -1) {
|
||||
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 {
|
||||
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,22 +9,29 @@ public enum FontAwesome5Icon {
|
||||
CHECK("\uF00C"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COPY("\uF0C5"), //
|
||||
EXCLAMATION("\uF12A"),
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
EXCLAMATION_TRIANGLE("\uF071"), //
|
||||
EYE("\uF06E"), //
|
||||
EYE_SLASH("\uF070"), //
|
||||
FILE_IMPORT("\uF56F"), //
|
||||
FOLDER_OPEN("\uF07C"), //
|
||||
HAND_HOLDING_HEART("\uF4BE"), //
|
||||
HEART("\uF004"), //
|
||||
HDD("\uF0A0"), //
|
||||
KEY("\uF084"), //
|
||||
LINK("\uF0C1"), //
|
||||
LOCK_ALT("\uF30D"), //
|
||||
LOCK_OPEN_ALT("\uF3C2"), //
|
||||
MINUS("\uF068"), //
|
||||
PLUS("\uF067"), //
|
||||
PRINT("\uF02F"), //
|
||||
QUESTION("\uF128"), //
|
||||
SPARKLES("\uF890"), //
|
||||
SPINNER("\uF110"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
USER_CROWN("\uF6A4"), //
|
||||
WRENCH("\uF0AD"), //
|
||||
;
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
public void requestFocus() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return passwordField.getText();
|
||||
}
|
||||
|
||||
public StringProperty textProperty() {
|
||||
return passwordField.textProperty();
|
||||
@@ -76,12 +80,16 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
return passwordField.getCharacters();
|
||||
}
|
||||
|
||||
public void setPassword(CharSequence password) {
|
||||
passwordField.setPassword(password);
|
||||
}
|
||||
|
||||
public void setPassword(char[] password) {
|
||||
passwordField.setPassword(password);
|
||||
}
|
||||
|
||||
public void swipe() {
|
||||
passwordField.swipe();;
|
||||
passwordField.swipe();
|
||||
}
|
||||
|
||||
public void selectAll() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -29,8 +30,8 @@ abstract class ForgetPasswordModule {
|
||||
@Provides
|
||||
@ForgetPasswordWindow
|
||||
@ForgetPasswordScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -46,13 +47,6 @@ abstract class ForgetPasswordModule {
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
||||
@ForgetPasswordScoped
|
||||
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders, @ForgetPasswordWindow Stage window) {
|
||||
return fxmlLoaders.createScene("/fxml/forget_password.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ForgetPasswordWindow
|
||||
@ForgetPasswordScoped
|
||||
@@ -64,6 +58,15 @@ abstract class ForgetPasswordModule {
|
||||
@ForgetPasswordWindow
|
||||
@ForgetPasswordScoped
|
||||
abstract ReadOnlyBooleanProperty bindReadOnlyConfirmedProperty(@ForgetPasswordWindow BooleanProperty confirmedProperty);
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
||||
@ForgetPasswordScoped
|
||||
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/forget_password.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ForgetPasswordScoped {
|
||||
@interface ForgetPasswordScoped {
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
@@ -16,8 +17,10 @@ import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiAppearance;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,8 +29,6 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class FxApplication extends Application {
|
||||
@@ -40,17 +41,21 @@ public class FxApplication extends Application {
|
||||
private final UnlockComponent.Builder unlockWindowBuilder;
|
||||
private final QuitComponent.Builder quitWindowBuilder;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
private final VaultService vaultService;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
|
||||
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
||||
|
||||
@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.mainWindow = mainWindow;
|
||||
this.preferencesWindow = preferencesWindow;
|
||||
this.unlockWindowBuilder = unlockWindowBuilder;
|
||||
this.quitWindowBuilder = quitWindowBuilder;
|
||||
this.macFunctions = macFunctions;
|
||||
this.vaultService = vaultService;
|
||||
this.licenseHolder = licenseHolder;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -81,9 +86,9 @@ public class FxApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public void showPreferencesWindow() {
|
||||
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = preferencesWindow.get().showPreferencesWindow();
|
||||
Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
|
||||
addVisibleStage(stage);
|
||||
LOG.debug("Showing Preferences");
|
||||
});
|
||||
@@ -113,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) {
|
||||
loadSelectedStyleSheet(newValue);
|
||||
}
|
||||
|
||||
private void loadSelectedStyleSheet(UiTheme theme) {
|
||||
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
|
||||
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
|
||||
switch (theme) {
|
||||
// case CUSTOM:
|
||||
// // TODO
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -44,6 +46,9 @@ abstract class FxApplicationModule {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract Application bindApplication(FxApplication application);
|
||||
|
||||
@Provides
|
||||
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
|
||||
|
||||
@@ -1,94 +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.ui.l10n;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class Localization extends ResourceBundle {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Localization.class);
|
||||
|
||||
private static final String LOCALIZATION_DEFAULT_FILE = "/localization/en.txt";
|
||||
private static final String LOCALIZATION_FILENAME_FMT = "/localization/%s.txt";
|
||||
|
||||
private final ResourceBundle fallback;
|
||||
private final ResourceBundle localized;
|
||||
|
||||
@Inject
|
||||
public Localization() {
|
||||
try {
|
||||
this.fallback = Objects.requireNonNull(loadLocalizationFile(LOCALIZATION_DEFAULT_FILE));
|
||||
LOG.debug("Loaded localization default file: {}", LOCALIZATION_DEFAULT_FILE);
|
||||
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
String region = Locale.getDefault().getCountry();
|
||||
LOG.debug("Detected language \"{}\" and region \"{}\"", language, region);
|
||||
|
||||
ResourceBundle localizationBundle = null;
|
||||
if (StringUtils.isNotEmpty(language) && StringUtils.isNotEmpty(region)) {
|
||||
String file = String.format(LOCALIZATION_FILENAME_FMT, language + "_" + region);
|
||||
LOG.trace("Attempting to load localization from: {}", file);
|
||||
localizationBundle = loadLocalizationFile(file);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(language) && localizationBundle == null) {
|
||||
String file = String.format(LOCALIZATION_FILENAME_FMT, language);
|
||||
LOG.trace("Attempting to load localization from: {}", file);
|
||||
localizationBundle = loadLocalizationFile(file);
|
||||
}
|
||||
if (localizationBundle == null) {
|
||||
LOG.debug("No localization found. Falling back to default language.");
|
||||
localizationBundle = this.fallback;
|
||||
}
|
||||
this.localized = Objects.requireNonNull(localizationBundle);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// returns null if no resource for given path
|
||||
private static ResourceBundle loadLocalizationFile(String resourcePath) throws IOException {
|
||||
try (InputStream in = Localization.class.getResourceAsStream(resourcePath)) {
|
||||
if (in != null) {
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
return new PropertyResourceBundle(reader);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleGetObject(String key) {
|
||||
return localized.containsKey(key) ? localized.getObject(key) : fallback.getObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getKeys() {
|
||||
Collection<String> keys = Sets.union(localized.keySet(), fallback.keySet());
|
||||
return Collections.enumeration(keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +1,39 @@
|
||||
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.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.HBox;
|
||||
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 javafx.scene.layout.StackPane;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FontLoader;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@MainWindowScoped
|
||||
public class MainWindowController implements FxController {
|
||||
|
||||
private static final String TITLE_FONT = "/css/dosis-bold.ttf";
|
||||
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 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 WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
public HBox titleBar;
|
||||
public VBox root;
|
||||
public Pane dragAndDropIndicator;
|
||||
public Region resizer;
|
||||
private double xOffset;
|
||||
private double yOffset;
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
public StackPane root;
|
||||
|
||||
@Inject
|
||||
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.minimizeToSysTray = minimizeToSysTray;
|
||||
this.updateChecker = updateChecker;
|
||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||
public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
}
|
||||
@@ -64,86 +41,70 @@ public class MainWindowController implements FxController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOG.debug("init MainWindowController");
|
||||
loadFont(TITLE_FONT);
|
||||
titleBar.setOnMousePressed(event -> {
|
||||
xOffset = event.getSceneX();
|
||||
yOffset = event.getSceneY();
|
||||
});
|
||||
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();
|
||||
});
|
||||
root.setOnDragEntered(this::handleDragEvent);
|
||||
root.setOnDragOver(this::handleDragEvent);
|
||||
root.setOnDragDropped(this::handleDragEvent);
|
||||
root.setOnDragExited(this::handleDragEvent);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||
return Stream.of(vaultListManager.add(pathToVault.getParent()));
|
||||
vaultListManager.add(pathToVault.getParent());
|
||||
} else {
|
||||
return Stream.of(vaultListManager.add(pathToVault));
|
||||
vaultListManager.add(pathToVault);
|
||||
}
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.debug("Not a vault: {}", pathToVault);
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
private void loadFont(String resourcePath) {
|
||||
try {
|
||||
FontLoader.load(resourcePath);
|
||||
} catch (FontLoader.FontLoaderException e) {
|
||||
LOG.warn("Error loading font from path: " + resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
if (minimizeToSysTray) {
|
||||
window.close();
|
||||
} else {
|
||||
window.setIconified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showPreferences() {
|
||||
application.showPreferencesWindow();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public BooleanBinding updateAvailableProperty() {
|
||||
return updateAvailable;
|
||||
public BooleanProperty draggingOverProperty() {
|
||||
return draggingOver;
|
||||
}
|
||||
|
||||
public boolean isUpdateAvailable() {
|
||||
return updateAvailable.get();
|
||||
public boolean isDraggingOver() {
|
||||
return draggingOver.get();
|
||||
}
|
||||
|
||||
public BooleanProperty draggingVaultOverProperty() {
|
||||
return draggingVaultOver;
|
||||
}
|
||||
|
||||
public boolean isDraggingVaultOver() {
|
||||
return draggingVaultOver.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -34,8 +35,8 @@ abstract class MainWindowModule {
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -57,15 +58,7 @@ abstract class MainWindowModule {
|
||||
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
||||
@MainWindowScoped
|
||||
static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders, MainWindowController mainWindowController, VaultListController vaultListController) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/main_window.fxml");
|
||||
|
||||
// still not perfect... cant't we have a global menubar via the AWT tray app?
|
||||
KeyCombination cmdN = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdN, vaultListController::didClickAddVault);
|
||||
scene.getAccelerators().put(cmdW, mainWindowController::close);
|
||||
|
||||
return scene;
|
||||
return fxmlLoaders.createScene("/fxml/main_window.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
@@ -75,6 +68,16 @@ abstract class MainWindowModule {
|
||||
@FxControllerKey(MainWindowController.class)
|
||||
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
|
||||
@IntoMap
|
||||
@FxControllerKey(VaultListController.class)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@MainWindowScoped
|
||||
public class MainWindowSceneFactory extends DefaultSceneFactory {
|
||||
|
||||
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
||||
|
||||
private final Lazy<MainWindowTitleController> mainWindowTitleController;
|
||||
private final Lazy<VaultListController> vaultListController;
|
||||
|
||||
@Inject
|
||||
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) {
|
||||
super(settings);
|
||||
this.mainWindowTitleController = mainWindowTitleController;
|
||||
this.vaultListController = vaultListController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
|
||||
} else {
|
||||
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-28
@@ -4,53 +4,32 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
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.Tasks;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnlockedController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
|
||||
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final ExecutorService executor;
|
||||
private final VaultService vaultService;
|
||||
|
||||
@Inject
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, ExecutorService executor) {
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, VaultService vaultService) {
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.vaultService = vaultService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void revealAccessLocation() {
|
||||
try {
|
||||
vault.get().reveal();
|
||||
} catch (Volume.VolumeException e) {
|
||||
LOG.error("Failed to reveal vault.", e);
|
||||
}
|
||||
vaultService.reveal(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void lock() {
|
||||
Vault v = vault.get();
|
||||
v.setState(VaultState.PROCESSING);
|
||||
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);
|
||||
vaultService.lock(vault.get(), false);
|
||||
// TODO count lock attempts, and allow forced lock
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
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.common.FxController;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
@@ -17,12 +21,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
//TODO: Add check if a vault in the list is invalid and add notification & controller
|
||||
@MainWindowScoped
|
||||
public class VaultListController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ObjectProperty<Vault> selectedVault;
|
||||
private final VaultListCellFactory cellFactory;
|
||||
@@ -33,8 +37,7 @@ public class VaultListController implements FxController {
|
||||
public ListView<Vault> vaultList;
|
||||
|
||||
@Inject
|
||||
VaultListController(@MainWindow Stage window, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
||||
this.window = window;
|
||||
VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
this.cellFactory = cellFactory;
|
||||
@@ -42,6 +45,7 @@ public class VaultListController implements FxController {
|
||||
this.removeVault = removeVault;
|
||||
this.noVaultSelected = selectedVault.isNull();
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -53,15 +57,28 @@ public class VaultListController implements FxController {
|
||||
if (c.wasAdded()) {
|
||||
Vault anyAddedVault = c.getAddedSubList().get(0);
|
||||
vaultList.getSelectionModel().select(anyAddedVault);
|
||||
window.setIconified(false);
|
||||
window.show();
|
||||
window.toFront();
|
||||
window.requestFocus(); // TODO: this beeps on macOS if there is a modal child window...
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
public void didClickAddVault() {
|
||||
addVaultWizard.build().showAddVaultWizard();
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
package org.cryptomator.ui.migration;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -26,7 +21,6 @@ import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
@Module
|
||||
abstract class MigrationModule {
|
||||
@@ -34,29 +28,19 @@ abstract class MigrationModule {
|
||||
@Provides
|
||||
@MigrationWindow
|
||||
@MigrationScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MigrationWindow
|
||||
@MigrationScoped
|
||||
static Map<KeyCodeCombination, Runnable> provideDefaultAccellerators(@MigrationWindow Set<Map.Entry<KeyCombination, Runnable>> accelerators) {
|
||||
return Map.ofEntries(accelerators.toArray(Map.Entry[]::new));
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MigrationWindow
|
||||
@MigrationScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @MigrationWindow Lazy<Map<KeyCodeCombination, Runnable>> accelerators) {
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("migration.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.sceneProperty().addListener(observable -> {
|
||||
stage.getScene().getAccelerators().putAll(accelerators.get());
|
||||
});
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
return stage;
|
||||
}
|
||||
@@ -84,19 +68,6 @@ abstract class MigrationModule {
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@MigrationWindow
|
||||
static Map.Entry<KeyCombination, Runnable> provideCloseWindowShortcut(@MigrationWindow Stage window) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return Map.entry(new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN), window::close);
|
||||
} else {
|
||||
return Map.entry(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), window::close);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(MigrationStartController.class)
|
||||
|
||||
@@ -7,8 +7,13 @@ import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
@@ -17,6 +22,7 @@ import javafx.util.Duration;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptofs.migration.Migrators;
|
||||
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
|
||||
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
@@ -39,6 +45,7 @@ public class MigrationRunController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MigrationRunController.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
private static final Duration MIGRATION_PROGRESS_UPDATE_INTERVAL = Duration.millis(25);
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
@@ -48,6 +55,9 @@ public class MigrationRunController implements FxController {
|
||||
private final Lazy<Scene> successScene;
|
||||
private final ObjectBinding<ContentDisplay> migrateButtonContentDisplay;
|
||||
private final BooleanProperty migrationButtonDisabled;
|
||||
private final DoubleProperty migrationProgress;
|
||||
private final ScheduledService<Double> migrationProgressObservationService;
|
||||
private volatile double volatileMigrationProgress = -1.0;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
|
||||
@Inject
|
||||
@@ -60,6 +70,10 @@ public class MigrationRunController implements FxController {
|
||||
this.successScene = successScene;
|
||||
this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty());
|
||||
this.migrationButtonDisabled = new SimpleBooleanProperty();
|
||||
this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress);
|
||||
this.migrationProgressObservationService = new MigrationProgressObservationService();
|
||||
migrationProgressObservationService.setExecutor(executor);
|
||||
migrationProgressObservationService.setPeriod(MIGRATION_PROGRESS_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -79,17 +93,18 @@ public class MigrationRunController implements FxController {
|
||||
LOG.info("Migrating vault {}", vault.getPath());
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
migrationProgressObservationService.start();
|
||||
Tasks.create(() -> {
|
||||
Migrators migrators = Migrators.get();
|
||||
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password);
|
||||
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged);
|
||||
return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME);
|
||||
}).onSuccess(needsAnotherMigration -> {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
|
||||
if (needsAnotherMigration) {
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
} else {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
passwordField.swipe();
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
@@ -105,9 +120,29 @@ public class MigrationRunController implements FxController {
|
||||
LOG.error("Migration failed for technical reasons.", e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
// TODO show generic error screen
|
||||
}).andFinally(() -> {
|
||||
migrationProgressObservationService.cancel();
|
||||
migrationProgressObservationService.reset();
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
// Called by a background task. We can not directly modify observable properties from here
|
||||
private void migrationProgressChanged(MigrationProgressListener.ProgressState state, double progress) {
|
||||
switch (state) {
|
||||
case INITIALIZING:
|
||||
volatileMigrationProgress = -1.0;
|
||||
break;
|
||||
case MIGRATING:
|
||||
volatileMigrationProgress = progress;
|
||||
break;
|
||||
case FINALIZING:
|
||||
volatileMigrationProgress = 1.0;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpted state " + state);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStoredPassword() {
|
||||
assert keychainAccess.isPresent();
|
||||
char[] storedPw = null;
|
||||
@@ -126,6 +161,27 @@ public class MigrationRunController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
// Sets migrationProgress to volatileMigrationProgress at its configured interval
|
||||
private class MigrationProgressObservationService extends ScheduledService<Double> {
|
||||
|
||||
@Override
|
||||
protected Task<Double> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Double call() {
|
||||
return volatileMigrationProgress;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
assert getValue() != null;
|
||||
migrationProgress.set(getValue());
|
||||
super.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
||||
private void shakeWindow() {
|
||||
@@ -180,4 +236,12 @@ public class MigrationRunController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty migrationProgressProperty() {
|
||||
return migrationProgress;
|
||||
}
|
||||
|
||||
public double getMigrationProgress() {
|
||||
return migrationProgress.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
+93
-1
@@ -1,27 +1,56 @@
|
||||
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.ChoiceBox;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.util.StringConverter;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@PreferencesScoped
|
||||
public class GeneralPreferencesController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
|
||||
|
||||
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 CheckBox startHiddenCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
public CheckBox autoStartCheckbox;
|
||||
public ToggleGroup nodeOrientation;
|
||||
public RadioButton nodeOrientationLtr;
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(Settings settings) {
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, LicenseHolder licenseHolder, ExecutorService executor) {
|
||||
this.settings = settings;
|
||||
this.trayMenuSupported = trayMenuSupported;
|
||||
this.autoStartStrategy = autoStartStrategy;
|
||||
this.licenseHolder = licenseHolder;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
themeChoiceBox.getItems().addAll(UiTheme.values());
|
||||
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
||||
@@ -30,6 +59,48 @@ public class GeneralPreferencesController implements FxController {
|
||||
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
|
||||
|
||||
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 */
|
||||
@@ -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.Subcomponent;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
|
||||
@FxmlScene(FxmlFile.PREFERENCES)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showPreferencesWindow() {
|
||||
ObjectProperty<SelectedPreferencesTab> selectedTabProperty();
|
||||
|
||||
default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||
selectedTabProperty().set(selectedTab);
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.show();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
@@ -15,26 +18,50 @@ import javax.inject.Inject;
|
||||
public class PreferencesController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||
private final BooleanBinding updateAvailable;
|
||||
public TabPane tabPane;
|
||||
public Tab generalTab;
|
||||
public Tab volumeTab;
|
||||
public Tab updatesTab;
|
||||
public Tab donationKeyTab;
|
||||
|
||||
@Inject
|
||||
public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) {
|
||||
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
|
||||
this.window = window;
|
||||
this.selectedTabProperty = selectedTabProperty;
|
||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
window.setOnShowing(this::windowWillAppear);
|
||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||
}
|
||||
|
||||
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
|
||||
if (updateAvailable.get()) {
|
||||
tabPane.getSelectionModel().select(updatesTab);
|
||||
private void selectChosenTab() {
|
||||
Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
|
||||
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,12 +4,12 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -22,14 +22,20 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@Module(includes = {AutoStartModule.class})
|
||||
abstract class PreferencesModule {
|
||||
|
||||
@Provides
|
||||
@PreferencesScoped
|
||||
static ObjectProperty<SelectedPreferencesTab> provideSelectedTabProperty() {
|
||||
return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@PreferencesWindow
|
||||
@PreferencesScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -46,13 +52,8 @@ abstract class PreferencesModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.PREFERENCES)
|
||||
@PreferencesScoped
|
||||
static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders, @PreferencesWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/preferences.fxml");
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/preferences.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
@@ -77,4 +78,9 @@ abstract class PreferencesModule {
|
||||
@FxControllerKey(VolumePreferencesController.class)
|
||||
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;
|
||||
|
||||
/**
|
||||
* TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported
|
||||
*/
|
||||
@PreferencesScoped
|
||||
public class VolumePreferencesController implements FxController {
|
||||
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
package org.cryptomator.ui.quit;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
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.VaultService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@QuitScoped
|
||||
public class QuitController implements FxController {
|
||||
@@ -30,15 +26,17 @@ public class QuitController implements FxController {
|
||||
private final Stage window;
|
||||
private final QuitResponse response;
|
||||
private final ObservableList<Vault> unlockedVaults;
|
||||
private final ExecutorService executor;
|
||||
private final ExecutorService executorService;
|
||||
private final VaultService vaultService;
|
||||
public Button lockAndQuitButton;
|
||||
|
||||
@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.response = response;
|
||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||
this.executor = executor;
|
||||
this.executorService = executorService;
|
||||
this.vaultService = vaultService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -52,84 +50,24 @@ public class QuitController implements FxController {
|
||||
public void lockAndQuit() {
|
||||
lockAndQuitButton.setDisable(true);
|
||||
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
|
||||
|
||||
Iterator<Vault> toBeLocked = List.copyOf(unlockedVaults).iterator();
|
||||
ScheduledService<Void> lockAllService = new LockAllVaultsService(executor, toBeLocked);
|
||||
lockAllService.setOnSucceeded(evt -> {
|
||||
if (!toBeLocked.hasNext()) {
|
||||
|
||||
Task<Collection<Vault>> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false);
|
||||
lockAllTask.setOnSucceeded(evt -> {
|
||||
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")));
|
||||
if (unlockedVaults.isEmpty()) {
|
||||
window.close();
|
||||
response.performQuit();
|
||||
}
|
||||
});
|
||||
lockAllService.setOnFailed(evt -> {
|
||||
lockAllTask.setOnFailed(evt -> {
|
||||
LOG.warn("Locking failed", lockAllTask.getException());
|
||||
lockAndQuitButton.setDisable(false);
|
||||
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -29,8 +30,8 @@ abstract class QuitModule {
|
||||
@Provides
|
||||
@QuitWindow
|
||||
@QuitScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -48,13 +49,8 @@ abstract class QuitModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.QUIT)
|
||||
@QuitScoped
|
||||
static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders, @QuitWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/quit.fxml");
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/quit.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import java.util.Optional;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
@Subcomponent(modules = {RecoveryKeyModule.class})
|
||||
public interface RecoveryKeyComponent {
|
||||
|
||||
@RecoveryKeyWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default void showRecoveryKeyCreationWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder vault(@RecoveryKeyWindow Vault vault);
|
||||
|
||||
@BindsInstance
|
||||
Builder owner(@Named("keyRecoveryOwner") Stage owner);
|
||||
|
||||
RecoveryKeyComponent build();
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyCreationController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
|
||||
@Inject
|
||||
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.successScene = successScene;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void createRecoveryKey() {
|
||||
Tasks.create(() -> {
|
||||
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
|
||||
}).onSuccess(result -> {
|
||||
recoveryKeyProperty.set(result);
|
||||
window.setScene(successScene.get());
|
||||
}).onError(IOException.class, e -> {
|
||||
LOG.error("Creation of recovery key failed.", e);
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
shakeWindow();
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
||||
private void shakeWindow() {
|
||||
WritableValue<Double> writableWindowX = new WritableValue<>() {
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return window.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double value) {
|
||||
window.setX(value);
|
||||
}
|
||||
};
|
||||
Timeline timeline = new Timeline( //
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), //
|
||||
new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), //
|
||||
new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), //
|
||||
new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), //
|
||||
new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), //
|
||||
new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), //
|
||||
new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), //
|
||||
new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) //
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
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 org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RecoveryKeyDisplayController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final String vaultName;
|
||||
private final String recoveryKey;
|
||||
|
||||
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
|
||||
this.window = window;
|
||||
this.vaultName = vaultName;
|
||||
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
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public boolean isPrinterSupported() {
|
||||
return Printer.getDefaultPrinter() != null;
|
||||
}
|
||||
|
||||
public String getRecoveryKey() {
|
||||
return recoveryKey;
|
||||
}
|
||||
|
||||
public String getVaultName() {
|
||||
return vaultName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.hash.Hashing;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Singleton
|
||||
public class RecoveryKeyFactory {
|
||||
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
|
||||
private final WordEncoder wordEncoder;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyFactory(WordEncoder wordEncoder) {
|
||||
this.wordEncoder = wordEncoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vaultPath Path to the storage location of a vault
|
||||
* @param password The vault's password
|
||||
* @return The recovery key of the vault at the given path
|
||||
* @throws IOException If the masterkey file could not be read
|
||||
* @throws InvalidPassphraseException If the provided password is wrong
|
||||
* @apiNote This is a long-running operation and should be invoked in a background thread
|
||||
*/
|
||||
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException {
|
||||
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, new byte[0], password);
|
||||
try {
|
||||
return createRecoveryKey(rawKey);
|
||||
} finally {
|
||||
Arrays.fill(rawKey, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String createRecoveryKey(byte[] rawKey) {
|
||||
Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes");
|
||||
byte[] paddedKey = Arrays.copyOf(rawKey, 66);
|
||||
try {
|
||||
// copy 16 most significant bits of CRC32(rawKey) to the end of paddedKey:
|
||||
Hashing.crc32().hashBytes(rawKey).writeBytesTo(paddedKey, 64, 2);
|
||||
return wordEncoder.encodePadded(paddedKey);
|
||||
} finally {
|
||||
Arrays.fill(paddedKey, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a String is a syntactically correct recovery key with a valid checksum
|
||||
* @param recoveryKey A word sequence which might be a recovery key
|
||||
* @return <code>true</code> if this seems to be a legitimate recovery key
|
||||
*/
|
||||
public boolean validateRecoveryKey(String recoveryKey) {
|
||||
final byte[] paddedKey;
|
||||
try {
|
||||
paddedKey = wordEncoder.decode(recoveryKey);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
if (paddedKey.length != 66) {
|
||||
return false;
|
||||
}
|
||||
byte[] rawKey = Arrays.copyOf(paddedKey, 64);
|
||||
byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66);
|
||||
byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes();
|
||||
byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2);
|
||||
return Arrays.equals(expectedCrc16, actualCrc16);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class RecoveryKeyModule {
|
||||
|
||||
@Provides
|
||||
@RecoveryKeyWindow
|
||||
@RecoveryKeyScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RecoveryKeyWindow
|
||||
@RecoveryKeyScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("keyRecoveryOwner") Stage owner) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("recoveryKey.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RecoveryKeyWindow
|
||||
@RecoveryKeyScoped
|
||||
static StringProperty provideRecoveryKeyProperty() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyCreationScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_create.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyCreationController.class)
|
||||
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyDisplayController.class)
|
||||
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,13 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RecoveryKeyScoped {
|
||||
|
||||
}
|
||||
+24
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface RecoveryKeyWindow {
|
||||
|
||||
}
|
||||
+7
-2
@@ -1,8 +1,10 @@
|
||||
package org.cryptomator.ui.keyrecovery;
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -14,16 +16,19 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Singleton
|
||||
class WordEncoder {
|
||||
|
||||
private static final String DEFAULT_WORD_FILE = "/i18n/4096words_en.txt";
|
||||
private static final int WORD_COUNT = 4096;
|
||||
private static final char DELIMITER = ' ';
|
||||
|
||||
private final List<String> words;
|
||||
private final Map<String, Integer> indices;
|
||||
|
||||
@Inject
|
||||
public WordEncoder() {
|
||||
this("/i18n/4096words_en.txt");
|
||||
this(DEFAULT_WORD_FILE);
|
||||
}
|
||||
|
||||
public WordEncoder(String wordFile) {
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -30,8 +31,8 @@ abstract class RemoveVaultModule {
|
||||
@Provides
|
||||
@RemoveVaultWindow
|
||||
@RemoveVaultScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -50,13 +51,8 @@ abstract class RemoveVaultModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.REMOVE_VAULT)
|
||||
@RemoveVaultScoped
|
||||
static Scene provideRemoveVaultScene(@RemoveVaultWindow FXMLLoaderFactory fxmlLoaders, @RemoveVaultWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/remove_vault.fxml");
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideRemoveVaultScene(@RemoveVaultWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/remove_vault.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@@ -3,11 +3,14 @@ package org.cryptomator.ui.traymenu;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,17 +40,17 @@ class TrayMenuController {
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final Settings settings;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final PopupMenu menu;
|
||||
private final AtomicBoolean allowSuddenTermination;
|
||||
|
||||
@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.fxApplicationStarter = fxApplicationStarter;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.settings = settings;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.vaults = vaults;
|
||||
this.menu = new PopupMenu();
|
||||
this.allowSuddenTermination = new AtomicBoolean(true);
|
||||
@@ -71,6 +74,7 @@ class TrayMenuController {
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
||||
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
||||
}
|
||||
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
|
||||
|
||||
// allow sudden termination
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||
@@ -112,6 +116,11 @@ class TrayMenuController {
|
||||
}
|
||||
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"));
|
||||
quitApplicationItem.addActionListener(this::quitApplication);
|
||||
menu.add(quitApplicationItem);
|
||||
@@ -126,11 +135,11 @@ class TrayMenuController {
|
||||
submenu.add(unlockItem);
|
||||
} else if (vault.isUnlocked()) {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -145,12 +154,24 @@ class TrayMenuController {
|
||||
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) {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -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.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.util.Arrays;
|
||||
@@ -49,19 +51,25 @@ public class UnlockController implements FxController {
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
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 BooleanProperty unlockButtonDisabled;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public CheckBox savePassword;
|
||||
|
||||
@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.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.successScene = successScene;
|
||||
this.invalidMountPointScene = invalidMountPointScene;
|
||||
this.genericErrorScene = genericErrorScene;
|
||||
this.genericErrorCause = genericErrorCause;
|
||||
this.forgetPassword = forgetPassword;
|
||||
this.unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
}
|
||||
@@ -100,17 +108,16 @@ public class UnlockController implements FxController {
|
||||
shakeWindow();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
}).onError(UnsupportedVaultFormatException.class, e -> {
|
||||
// TODO
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
// TODO
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||
// TODO
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
// TODO
|
||||
genericErrorCause.set(e);
|
||||
window.setScene(genericErrorScene.get());
|
||||
}).andFinally(() -> {
|
||||
if (!vault.isUnlocked()) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
+39
@@ -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,18 +4,19 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StackTraceController;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -24,14 +25,14 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents={ForgetPasswordComponent.class})
|
||||
@Module(subcomponents = {ForgetPasswordComponent.class})
|
||||
abstract class UnlockModule {
|
||||
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -46,28 +47,40 @@ abstract class UnlockModule {
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("genericErrorCause")
|
||||
@UnlockScoped
|
||||
static ObjectProperty<Exception> provideGenericErrorCause() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.UNLOCK)
|
||||
@UnlockScoped
|
||||
static Scene provideUnlockScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders, @UnlockWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/unlock2.fxml"); // TODO rename fxml file
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideUnlockScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/unlock.fxml");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
|
||||
@UnlockScoped
|
||||
static Scene provideUnlockSuccessScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders, @UnlockWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/unlock_success.fxml");
|
||||
static Scene provideUnlockSuccessScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
|
||||
}
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
|
||||
@UnlockScoped
|
||||
static Scene provideInvalidMountPointScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/unlock_invalid_mount_point.fxml");
|
||||
}
|
||||
|
||||
return scene;
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR)
|
||||
@UnlockScoped
|
||||
static Scene provideGenericErrorScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/unlock_generic_error.fxml");
|
||||
}
|
||||
|
||||
|
||||
@@ -83,5 +96,22 @@ abstract class UnlockModule {
|
||||
@FxControllerKey(UnlockSuccessController.class)
|
||||
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.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
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;
|
||||
|
||||
@@ -26,14 +26,16 @@ public class UnlockSuccessController implements FxController {
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final VaultService vaultService;
|
||||
private final ObjectProperty<ContentDisplay> revealButtonState;
|
||||
private final BooleanProperty revealButtonDisabled;
|
||||
|
||||
@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.vault = vault;
|
||||
this.executor = executor;
|
||||
this.vaultService = vaultService;
|
||||
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
|
||||
this.revealButtonDisabled = new SimpleBooleanProperty();
|
||||
}
|
||||
@@ -49,17 +51,19 @@ public class UnlockSuccessController implements FxController {
|
||||
LOG.trace("UnlockSuccessController.revealAndClose()");
|
||||
revealButtonState.set(ContentDisplay.LEFT);
|
||||
revealButtonDisabled.set(true);
|
||||
Tasks.create(() -> {
|
||||
vault.reveal();
|
||||
}).onSuccess(() -> {
|
||||
window.close();
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
// TODO
|
||||
LOG.warn("Reveal failed.", e);
|
||||
}).andFinally(() -> {
|
||||
|
||||
Task<Vault> revealTask = vaultService.createRevealTask(vault);
|
||||
revealTask.setOnSucceeded(evt -> {
|
||||
revealButtonState.set(ContentDisplay.TEXT_ONLY);
|
||||
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 */
|
||||
|
||||
+9
-1
@@ -5,6 +5,7 @@ import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -14,12 +15,14 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||
|
||||
@Inject
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow) {
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.changePasswordWindow = changePasswordWindow;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -27,4 +30,9 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,34 +1,76 @@
|
||||
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.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.RadioButton;
|
||||
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.WindowsDriveLetters;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
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
|
||||
public class MountOptionsController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
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 CheckBox readOnlyCheckbox;
|
||||
public CheckBox customMountFlagsCheckbox;
|
||||
public TextField mountFlags;
|
||||
public ToggleGroup mountPoint;
|
||||
public RadioButton mountPointAuto;
|
||||
public RadioButton mountPointWinDriveLetter;
|
||||
public RadioButton mountPointCustomDir;
|
||||
public ChoiceBox<String> driveLetterSelection;
|
||||
|
||||
@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.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows);
|
||||
this.windowsDriveLetters = windowsDriveLetters;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName());
|
||||
|
||||
// readonly:
|
||||
readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode());
|
||||
mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
||||
readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty());
|
||||
|
||||
// custom mount flags:
|
||||
mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not());
|
||||
customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags());
|
||||
if (vault.isHavingCustomMountFlags()) {
|
||||
mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags());
|
||||
@@ -36,6 +78,27 @@ public class MountOptionsController implements FxController {
|
||||
} else {
|
||||
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
|
||||
@@ -51,4 +114,81 @@ public class MountOptionsController implements FxController {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,20 +6,18 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -27,14 +25,14 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {ChangePasswordComponent.class})
|
||||
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
|
||||
abstract class VaultOptionsModule {
|
||||
|
||||
@Provides
|
||||
@VaultOptionsWindow
|
||||
@VaultOptionsScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -53,13 +51,8 @@ abstract class VaultOptionsModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.VAULT_OPTIONS)
|
||||
@VaultOptionsScoped
|
||||
static Scene provideVaultOptionsScene(@VaultOptionsWindow FXMLLoaderFactory fxmlLoaders, @VaultOptionsWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/vault_options.fxml");
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideVaultOptionsScene(@VaultOptionsWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/vault_options.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@@ -6,11 +6,9 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -29,8 +27,8 @@ abstract class WrongFileAlertModule {
|
||||
@Provides
|
||||
@WrongFileAlertWindow
|
||||
@WrongFileAlertScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, resourceBundle);
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -48,13 +46,8 @@ abstract class WrongFileAlertModule {
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.WRONGFILEALERT)
|
||||
@WrongFileAlertScoped
|
||||
static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FXMLLoaderFactory fxmlLoaders, @WrongFileAlertWindow Stage window) {
|
||||
Scene scene = fxmlLoaders.createScene("/fxml/wrongfilealert.fxml"); // TODO rename fxml file
|
||||
|
||||
KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
scene.getAccelerators().put(cmdW, window::close);
|
||||
|
||||
return scene;
|
||||
static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/wrongfilealert.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
apple.applescript.AppleScriptEngineFactory
|
||||
@@ -16,6 +16,10 @@
|
||||
src: url('opensans-bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('dosis-bold.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -74,8 +78,10 @@
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_4;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_7;
|
||||
PROGRESS_INDICATOR_END: GRAY_5;
|
||||
PROGRESS_BAR_BG: GRAY_2;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_3;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
|
||||
@@ -111,6 +117,10 @@
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.label-extra-small {
|
||||
-fx-font-size: 0.64em;
|
||||
}
|
||||
|
||||
.text-flow > * {
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
@@ -174,20 +184,6 @@
|
||||
-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 {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
@@ -196,6 +192,16 @@
|
||||
-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 *
|
||||
@@ -207,34 +213,36 @@
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area {
|
||||
-fx-padding: 6px 12px 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 0 0 1px 0;
|
||||
-fx-padding: 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
||||
-fx-background-insets: 0 0 1px 0, 1px;
|
||||
-fx-background-radius: 4px 4px 0 0;
|
||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
||||
.tab-pane .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
-fx-padding: 6px 12px;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
||||
-fx-fill: TEXT_FILL;
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -460,6 +468,42 @@
|
||||
-fx-padding: 4px 6px 4px 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Text Areas *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.text-area {
|
||||
-fx-cursor: default;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.text-input:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
.text-area > .scroll-pane > .scroll-bar {
|
||||
-fx-padding: 2px;
|
||||
}
|
||||
|
||||
.text-area .content {
|
||||
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Buttons *
|
||||
@@ -631,7 +675,6 @@
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -642,6 +685,18 @@
|
||||
-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 {
|
||||
-fx-padding: 0 0 0 0.3em;
|
||||
}
|
||||
@@ -653,6 +708,10 @@
|
||||
-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 {
|
||||
-fx-translate-x: -1.4em;
|
||||
}
|
||||
@@ -776,3 +835,31 @@
|
||||
.progress-indicator:indeterminate .segment11 {
|
||||
-fx-shape:"m 14.321262,6.5816808 c -0.824944,0.3797564 -0.10368,1.8484772 0.718513,1.3544717 L 18.786514,5.9486042 C 19.644992,5.4932031 18.92648,4.1387308 18.068001,4.5941315 z";
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ProgressBar *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.progress-bar {
|
||||
-fx-indeterminate-bar-length: 100;
|
||||
-fx-indeterminate-bar-escape: true;
|
||||
-fx-indeterminate-bar-flip: true;
|
||||
-fx-indeterminate-bar-animation-time: 2;
|
||||
}
|
||||
|
||||
.progress-bar > .bar {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 0.5em;
|
||||
}
|
||||
|
||||
.progress-bar:indeterminate > .bar {
|
||||
-fx-background-color: linear-gradient(to left, transparent, CONTROL_PRIMARY_BG_NORMAL);
|
||||
}
|
||||
|
||||
.progress-bar > .track {
|
||||
-fx-background-color: PROGRESS_BAR_BG;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
src: url('opensans-bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('dosis-bold.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -74,8 +78,10 @@
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_6;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_2;
|
||||
PROGRESS_INDICATOR_END: GRAY_4;
|
||||
PROGRESS_BAR_BG: GRAY_8;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG: GRAY_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5;
|
||||
PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5;
|
||||
@@ -111,6 +117,10 @@
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.label-extra-small {
|
||||
-fx-font-size: 0.64em;
|
||||
}
|
||||
|
||||
.text-flow > * {
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
@@ -174,20 +184,6 @@
|
||||
-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 {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
@@ -196,6 +192,16 @@
|
||||
-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 *
|
||||
@@ -207,34 +213,36 @@
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area {
|
||||
-fx-padding: 6px 12px 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 0 0 1px 0;
|
||||
-fx-padding: 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
||||
-fx-background-insets: 0 0 1px 0, 1px;
|
||||
-fx-background-radius: 4px 4px 0 0;
|
||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
||||
.tab-pane .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
-fx-padding: 6px 12px;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
||||
-fx-fill: TEXT_FILL;
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -460,6 +468,42 @@
|
||||
-fx-padding: 4px 6px 4px 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Text Areas *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.text-area {
|
||||
-fx-cursor: default;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.text-input:focused {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||
}
|
||||
|
||||
.text-input:disabled {
|
||||
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||
}
|
||||
|
||||
.text-area > .scroll-pane > .scroll-bar {
|
||||
-fx-padding: 2px;
|
||||
}
|
||||
|
||||
.text-area .content {
|
||||
-fx-padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
-fx-cursor: text;
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-highlight-fill: PRIMARY_BG;
|
||||
-fx-prompt-text-fill: TEXT_FILL_SECONDARY;
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Buttons *
|
||||
@@ -631,7 +675,6 @@
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -642,6 +685,18 @@
|
||||
-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 {
|
||||
-fx-padding: 0 0 0 0.3em;
|
||||
}
|
||||
@@ -653,6 +708,10 @@
|
||||
-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 {
|
||||
-fx-translate-x: -1.4em;
|
||||
}
|
||||
@@ -776,3 +835,31 @@
|
||||
.progress-indicator:indeterminate .segment11 {
|
||||
-fx-shape:"m 14.321262,6.5816808 c -0.824944,0.3797564 -0.10368,1.8484772 0.718513,1.3544717 L 18.786514,5.9486042 C 19.644992,5.4932031 18.92648,4.1387308 18.068001,4.5941315 z";
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ProgressBar *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.progress-bar {
|
||||
-fx-indeterminate-bar-length: 100;
|
||||
-fx-indeterminate-bar-escape: true;
|
||||
-fx-indeterminate-bar-flip: true;
|
||||
-fx-indeterminate-bar-animation-time: 2;
|
||||
}
|
||||
|
||||
.progress-bar > .bar {
|
||||
-fx-background-color: CONTROL_PRIMARY_BG_NORMAL;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 0.5em;
|
||||
}
|
||||
|
||||
.progress-bar:indeterminate > .bar {
|
||||
-fx-background-color: linear-gradient(to left, transparent, CONTROL_PRIMARY_BG_NORMAL);
|
||||
}
|
||||
|
||||
.progress-bar > .track {
|
||||
-fx-background-color: PROGRESS_BAR_BG;
|
||||
-fx-background-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER">
|
||||
<padding>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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.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.addvaultwizard.AddVaultFailureExistingController"
|
||||
minWidth="450"
|
||||
maxWidth="450"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<HBox spacing="12" alignment="CENTER_LEFT" 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>
|
||||
<FormattedLabel format="%addvaultwizard.existing.error" arg1="${controller.vaultName}" wrapText="true"/>
|
||||
</HBox>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="B+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user