Compare commits

..

54 Commits

Author SHA1 Message Date
Sebastian Stenzel
45ad496f41 Merge branch 'release/1.4.0-beta2' 2018-07-12 15:21:50 +02:00
Sebastian Stenzel
a272bf2614 preparing 1.4.0-beta2 2018-07-12 15:14:36 +02:00
infeo
119e0724d5 fixing broken option "reveal after mount" 2018-07-12 14:58:34 +02:00
Sebastian Stenzel
76a9cb9a06 using different threadpools for normal ExecutorService (will spawn threads on demand) and ScheduledExecutorService (limited to 4 scheduler threads) 2018-07-12 14:53:27 +02:00
infeo
acb8d4cd0c fixing display issue when locking a vault and changign to another vault during the lock process 2018-07-12 11:53:44 +02:00
Sebastian Stenzel
ffe8887114 no longer using jdk.incubator.httpclient, because windows app doesn't start with --add-modules arg in cfg file 2018-07-12 11:03:35 +02:00
Sebastian Stenzel
d8c8c2380a trying to move coverage report to end of build to workaround https://github.com/travis-ci/travis-cookbooks/issues/989 2018-07-11 16:12:07 +02:00
Sebastian Stenzel
fe5ce79802 updated mockito (restoring jdk 10 compatibility) 2018-07-11 15:52:07 +02:00
Sebastian Stenzel
bf7a8686a6 fix build 2018-07-11 15:45:05 +02:00
Sebastian Stenzel
143070d02d Updated to JDK 10 2018-07-11 15:40:28 +02:00
Sebastian Stenzel
06827a7466 changed ant-kit format to zip (because windows..) 2018-07-10 22:57:33 +02:00
Sebastian Stenzel
5add862ce8 updated ant-kit 2018-07-10 17:44:22 +02:00
Sebastian Stenzel
2b8d7c6c3b linux logback configuration moved to https://github.com/cryptomator/cryptomator-linux
[ci skip]
2018-07-10 17:05:45 +02:00
Sebastian Stenzel
f5da13d3b4 Updated ant-kit. To be used by Windows and Mac build as well 2018-07-10 16:59:07 +02:00
Sebastian Stenzel
03dfd3e887 Replaced AsyncTaskService by new Tasks utility using javafx.concurrent API 2018-07-10 14:51:33 +02:00
infeo
8241559362 Merge branch 'develop' of https://github.com/cryptomator/cryptomator into develop 2018-07-09 18:43:51 +02:00
infeo
c289040624 fixes a display error where a vault appeared unlocked even when a wrong password was given 2018-07-09 18:43:45 +02:00
Sebastian Stenzel
767acbd922 Merge pull request #689 from jellemdekker/feature/296-status-indicator-in-system-tray
[Help needed] Different system tray icon is shown when one or more vaults are unlocked

fixes #296
2018-07-09 17:54:47 +02:00
Tobias Hagemann
ccd4cedb08 Unlocked tray icons for macOS 2018-07-09 17:37:16 +02:00
jellemdekker
6a5a1e5bae Replaced spaces in indentation with tabs. 2018-07-09 14:56:22 +02:00
jellemdekker
b3d76bb394 Created the unlocked version of the tray icon for every operating system except macOS. Uses the free-for-commercial-use 'Lock, open icon' from the Hawcons icon set by Yannick Lung over at Iconfinder.com: https://www.iconfinder.com/icons/314694/lock_open_icon 2018-07-06 17:43:43 +02:00
jellemdekker
1924a7dec9 Implemented that a different system tray icon is shown to indicate when one or more vaults are unlocked. The unlocked icons are placeholders and will updated visually in following commits. 2018-07-06 17:34:39 +02:00
Sebastian Stenzel
b65da30899 Merge pull request #688 from Gregvh/fix-broken-links
Fix broken links in CONTRIBUTING.md
[ci skip]
2018-07-06 17:33:56 +02:00
Sebastian Stenzel
7de8b3da02 Merge pull request #686 from jellemdekker/bugfix/update-readme.md-build-dependency-java
Updated minimum required version of Java Development Kit
[ci skip]
2018-07-06 17:33:02 +02:00
Gregvh
48ac8da1a7 Fix broken links in CONTRIBUTING.md 2018-07-06 14:13:15 +02:00
jellemdekker
86ae35c7eb Updated minimum required version of Java Development Kit, because this has changed since Cryptomator version 1.4.0. Source: https://community.cryptomator.org/t/how-to-run-cryptomator-1-4-0-beta1-jar/1599/4 . Skipping over Java 9, because its support has already ended. Also, JCE unlimited strength policy files are now included in the JDK by default, so they no longer need to be mentioned separately. 2018-07-06 03:52:04 +02:00
infeo
8421a8fc7b updating version of dokany-nio-adapter 2018-07-05 16:44:13 +02:00
infeo
b579e03bc8 workaround for the combination of windows + dokany + (automatic drive letter selection) 2018-06-28 16:55:00 +02:00
infeo
9217b11e61 adding automatic drive letter selection to dokany volume 2018-06-28 16:02:11 +02:00
Tobias Hagemann
e16bd7373c updated dokany version [ci skip] 2018-06-22 17:00:48 +02:00
Sebastian Stenzel
ef53561bf0 Improvements suggested in #598 2018-06-21 17:47:47 +02:00
Sebastian Stenzel
3165c4ba86 Add GPG signature for antkit.tar.gz, fixes #522 2018-06-17 23:22:48 +02:00
Sebastian Stenzel
f1bf157cac Update stale.yml
[ci skip]
2018-06-17 16:20:00 +02:00
Sebastian Stenzel
9c75dd48dd Create no-response.yml
[ci skip]
2018-06-17 15:55:09 +02:00
Sebastian Stenzel
74d4b4ea47 Rename style.yml to stale.yml
[ci skip]
2018-06-17 15:49:19 +02:00
Sebastian Stenzel
6f66f4cbf1 Create style.yml
[ci skip]
2018-06-17 15:48:59 +02:00
Sebastian Stenzel
874c5506a7 Added Dokany to volume type setting.
Restart no longer required, when changing preferred volume type.
References #207
2018-06-17 14:19:22 +02:00
Sebastian Stenzel
aed35c17c8 Added Dokany Support
fixes #207
2018-06-17 13:59:28 +02:00
Sebastian Stenzel
873e438759 merging jacoco reports via codacy, subproject no longer needed 2018-06-17 11:58:26 +02:00
Sebastian Stenzel
5b45893c7b Renamed CommandFailedException to VolumeException 2018-06-15 09:48:46 +02:00
Sebastian Stenzel
5515258af1 updated FUSE, fixed reveal after unlock 2018-06-14 17:37:32 +02:00
Sebastian Stenzel
dd5d52d25a updated dependencies 2018-05-24 14:19:59 +02:00
Sebastian Stenzel
24236f3844 Merge commit '458866f7d6460e688897b851248c8a2c22dc80d1' into develop 2018-05-24 14:14:44 +02:00
Markus Kreusch
458866f7d6 Asking user once before enabling update check 2018-05-22 11:36:00 +02:00
Sebastian Stenzel
525b0a7982 updated dependencies 2018-05-14 21:38:22 +02:00
Sebastian Stenzel
d53af61b58 Removed "Mount after unlock" option. Mounting/unmounting no longer visible to the user, but merged with unlocking/locking. 2018-05-14 21:37:38 +02:00
Sebastian Stenzel
b0ab46b7b6 Removed copy WebDAV URL button 2018-05-14 21:04:00 +02:00
infeo
9107d296c3 fixes partially #660 2018-04-09 18:07:04 +02:00
Sebastian Stenzel
6be95963a1 moved issue templates to .github directory
[ci skip]
2018-04-07 00:42:08 +02:00
Sebastian Stenzel
09c9361e94 Merge branch 'release/1.4.0-beta1' into develop
[ci skip]
2018-04-06 16:55:22 +02:00
Sebastian Stenzel
5e7cea216d Merge branch 'release/1.4.0-beta1' 2018-04-06 16:54:12 +02:00
Sebastian Stenzel
6d91992102 preparing 1.4.0-beta1 2018-04-06 16:31:08 +02:00
Sebastian Stenzel
5a23ee0be6 updated dependencies 2018-04-06 16:29:40 +02:00
Sebastian Stenzel
31e186dd15 bumped fuse version 2018-04-05 23:24:47 +02:00
57 changed files with 885 additions and 885 deletions

View File

@@ -4,7 +4,7 @@
- Ensure you're running the latest version of Cryptomator.
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/faq).
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq).
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new).
## Did you write a patch that fixes a bug?
@@ -18,7 +18,7 @@
## Code of Conduct
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/CODE_OF_CONDUCT.md).
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
## Above all, thank you for your contributions

View File

@@ -4,8 +4,8 @@ Before creating a new issue make sure that you
- searched existing (and closed) issues: https://github.com/cryptomator/cryptomator/issues
- searched the knowledge base: https://community.cryptomator.org/c/kb
- have read the support guide: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
- have read the contribution guide: https://github.com/cryptomator/cryptomator/blob/develop/CONTRIBUTING.md
- have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/CODE_OF_CONDUCT.md
- have read the contribution guide: https://github.com/cryptomator/cryptomator/blob/develop/.github/CONTRIBUTING.md
- have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
## Basic Info

View File

@@ -0,0 +1,15 @@
## 1.4.0 Beta Issue Checklist
- Existing 1.4.0 Beta Issues: https://github.com/cryptomator/cryptomator/milestone/27
- Contribution Guide: https://github.com/cryptomator/cryptomator/blob/develop/.github/CONTRIBUTING.md
- Code of Conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
## Software Used During the Test
- Cryptomator 1.4.0 Beta 1
- Ubuntu 16.04 / macOS 10.11.6 / etc
- Linux Kernel x.y.z
- Gnome x.y.z
- OpenOffice x.y.z
- ...
## Description
What doesn't work? What did you do? How can the bug be reproduced?

13
.github/no-response.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 14
# Label requiring a response
responseRequiredLabel: state:awaiting-response
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

19
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- type:security-issue # never close automatically
- state:awaiting-response # handled by different bot
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: state:stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -1,7 +1,7 @@
language: java
sudo: false
jdk:
- oraclejdk9
- oraclejdk10
branches:
except:
- continuous # To avoid infinite loops, as this tag is created by this Travis config
@@ -14,6 +14,7 @@ env:
- secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY
- secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN
- secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY
- secure: "PiH/o9MLOyPdjIwECIEfj3TuUxx7QB0CIs3o1WXjqb1PbDdHDbQyvswYit6xDw9NrJp/A+ov2k00jq+n+8fLTSd4AY21y5WiJN/ccCTWUuUiFhGxOyM37aeWAPAn4rUp7D7o8jLxEdpGZAfglIzaz+GCEQYxfV/w3FDwztViXgQ=" # GPG_PASSPHRASE
addons:
apt:
packages:
@@ -28,13 +29,8 @@ 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
script:
- mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage
after_success:
- jdk_switcher use oraclejdk8
- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/2.0.1/codacy-coverage-reporter-2.0.1-assembly.jar
- $JAVA_HOME/bin/java -cp ~/codacy-coverage-reporter-assembly-latest.jar com.codacy.CodacyCoverageReporter -l Java -r main/jacoco-report/target/site/jacoco-aggregate/jacoco.xml
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
before_deploy:
- jdk_switcher use oraclejdk9
- |
if [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
CONTINUOUS_RELEASE_URL=`curl -s https://api.github.com/repos/cryptomator/cryptomator/releases/tags/continuous | jq -re '.url'`
@@ -54,6 +50,8 @@ before_deploy:
git remote remove gh
fi
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
- gpg --import 34C80F11.gpg
- gpg --detach-sign -a -u 34C80F11 --batch --passphrase ${GPG_PASSPHRASE} main/ant-kit/target/antkit.zip
deploy:
- provider: releases # CONTINUOUS
prerelease: true
@@ -63,7 +61,8 @@ deploy:
file_glob: true
file:
- "main/uber-jar/target/Cryptomator-*.jar"
- "main/ant-kit/target/antkit.tar.gz"
- "main/ant-kit/target/antkit.zip"
- "main/ant-kit/target/antkit.zip.asc"
skip_cleanup: true
name: Cryptomator continuous build
body: Automatically built on $(date +'%F %T %Z').
@@ -76,7 +75,8 @@ deploy:
api_key: $GITHUB_API_KEY
file:
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
- "main/ant-kit/target/antkit.tar.gz"
- "main/ant-kit/target/antkit.zip"
- "main/ant-kit/target/antkit.zip.asc"
skip_cleanup: true
on:
repo: cryptomator/cryptomator
@@ -91,3 +91,11 @@ deploy:
on:
repo: cryptomator/cryptomator
tags: true
after_script:
- jdk_switcher use oraclejdk8
- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.1/codacy-coverage-reporter-4.0.1-assembly.jar
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar final

57
34C80F11.gpg Normal file
View File

@@ -0,0 +1,57 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v1
lQc+BFdtLXQBEACzObgsAnfD2JInQ2J7BDv0kARpfDLaNIbQJxdnSUZxJk7yOGge
64juAzkIDBq4jE5fy3ErZCHaEceDj/mVaAvIlygrQ5KZGzFCi9dZXWjKW/VBvVm0
hPUbr0NWTEMnZSXCAL03L1LVjHjfSMRAwl9gClwlff9eKW8gVIG+2gww+wQO0xnY
y6DO6xEtfxxz+hoHsygEDh+qpONSoffEWhoTdn6qh6jJ72sOi6azGqFA30mxWM6i
YNp3zrH0q8IrjS1KqzaVg7frfeok5CtPZCgdfDNBDZA/Dfbu1BxpRWEleDMLJ6zz
lh4MIKC6RqDLjkzpygP+r7XKPve/hJ6nhH9FQZqASlrjn8KheaOcLqRgScDmC9Bq
VbxgieBUpVfjj8KclxiQh51jYHPIp/1QIgwvGEY5R1Wb8QzLnNkIxWzLDKzAG5XB
F1jb5JMmwoK9dwv+X9jiPh/xZpu6dh/5o13NQoSS6/p7AdlRXyDiaNtij0VOP1Jt
UlSCPp01peyLlKr8cDdI1UQadYpbmOzMplDKD2Yf+QSiyYdIYPPrfn8tNUyhbZH8
jATI26l1ctL133Gd9o0SYoBleNPNiQqhk9T0AcaOHujQDH/dM0+9c5w/zgPkDFpQ
S/uMFEq4yMok4AARd8qBRATrGYL2sFtB4HKEXtQw/SlaaGL41EJyvpZnbwARAQAB
/gMDAoX3Q3p+1aU3YKQDwxMDDPerrO0eT7bUGdo9A3usVEHSZ/uG2hcIHYpgW/j3
nNmaoLVDEgkbpPub7tlCr6+e26J53otqYXmLSWxx0jmFGivSVRi6nyZz+HUKObCE
qi70z3fEZ8rdL6Rj24UnIHvu+9RvoW8GeoNZoy7G/hC60mPSQUvU0DE9NfMUiZVx
nxXqLlR/rwOOIMmW5FFPCFRe18oRuLZ/+AcN+COjciGE3Fj7fpCybc2rKHKsKIO9
OkMYhuHZSSxjh8Dsfaen7XOwp5S+UashEhV5d9NaSvpEgdCtH+JwAHrXOlJrUU4x
faRkJsFoej6DXdRL/vr91d2eB60A5Qtg3H+/9n/TQW/Low487h/j6Lod6JxoeYKj
L+UmIGprSn4WZYwJiLfBB/v3T33e4cHGwMiGyU4aR8KhMsv5c8UmgDqpFkeY7Sqw
accRDujoaIZviSlPgLG7UneqVk8+WR4LFtCwKXFyObTpx9JyzPYUgIj49WzHDAK+
TnYJROiC+HGetaRRG0lSS6o6eoBe+YXBl0y84+htQ8YPojDdOZ68tNbfrWKbbxG7
uOWquP30+Z+J8tlL/3o4cK3x9rYNkroreR1dA9He9Jev3/z3GzuuZAmLSIRlh6Cd
yPbn1kOfEl3L03Ty5sriJwSgdindDm3wiem0E+lkUSIYvrGnCVOPTZsTqe9+0DJz
KW5bzzIJRaCylT0ec9GTSqGvMIpqoFH8yuf5k0w0pkjhLUqDEKxgnvn2lp79eKjn
W22VHecGv98867IS7Y150ch6YSHCAcfT2D2LFD8B9NmSI3ue8BOwfq5nLEtGYmyb
+PBHKUGxU77xlaywVyxA451ok/3plg9fiGwCU605zdpAfoO1TToIi9/m3EDoJ6iv
rmV+Ox9O30W7B5hdHNgt4Lkf1LmQNatokzTcKjNIOJiWpQLgu619tMEEtIuHE7rF
lQZsVBYLHpmAZo1oQ5AqCQXlTdAp6l/ZzSh2sU5dATAxAy3hCM8zs2AawBrr9jdP
Xsj1A5aSQN/V4cbRg0VS4C0i/Z5t3Q2r+casMv3KCe+4wIwfkzeMPxRCzC4svMvp
FjnwbBUi+I3PvrOpFtIQo0hXM5wNZFujqR2nR6A+NcvfDyM30UaAs8KeSDvrDZrk
bt4W4NOfDxJrQLEeej9lMuZdI1SaoRFM2C8DO1045FKcGBeQWj5rfG9szbF2Bjp5
ZXY7yJiBi4bnl7FlJbcB6yO1N21wDmzfULx7XyyNAxQNyfId0Mcd6QKgNBsHnWsq
YPxfyCuP21G4Rhfg+Dsp4N5EUdczKFfAz+JyMkLp902OE+dM6y8Qim5HgLWsfKnO
5A6O18um2aMSSC01hmSZB6bAVH2yF66K+eBp2B1QkStNOKBel4xM0XmXfPfSxyrF
+cjTtyThstjZWUGCujGCmNyvN49/ouMkLLvLrKcN/ecZN6+ZsJokIdo1KJ2RPNbm
2I5lEKh3piZLMAim5aWQClS+GfF9UVR1jpGisQlw//cE8Afax1XASOaAiXWa7PzR
dss9SfASndOjWiB2FAyDsvqtn5vyDE/1UQHxkyNZyrA8IhPk5ytn7WTz+rczM6ZQ
cME1EMdwBVsftS/S/oTDRX9ms/78QLMbBA7qB+aQ5atQGvsV9TrIWeAv2AlYa/f7
3AFhPPY1XmmhNo1IwHQmVMoUFCI73X8LZnHHIXVV6zyGFfC/+2EwFu/c1wm9y2ct
wvHXLSm1cQNAFvSSNMeeLhpXImy1UFwbUulYZ44d//zQtDZDcnlwdG9ib3QgKFJl
bGVhc2UgTWFuYWdlcikgPHJlbGVhc2VzQGNyeXB0b21hdG9yLm9yZz6JAj4EEwEC
ACgFAldtLXQCGwMFCQWjmoAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFCc
nWM0yA8RW44P/2tDxJz5Ps4xYRIZie2gXZuK0Q6mpQATYX+5IZyAPBUdO5PmvwnI
CWI4uGxosJxtM9eL6iiOMIN6cxQdOMDQtdmeOW/CYM731TjtSNseIre/8Kc4O9uE
JxeeoT3Os7QUNUHMxLhfAI1gaJcYsSAJqvmLpiyOoH20n7FVxKv8B4JqG9b4zK8H
Ol9oQXMnfZA9XIYciOE2EpVD4xLg2+v4qbFaLM86ogA6xjdsImD+HqMO0icnh+Vo
Bf2EiwUieWemX6kBh37zuoQWWX1O+9BWvJ0Rk8xDWUM6dGU7Z/+cwXzz7UuXOxWT
YQlYLLgaC8HexyhrDAkOvcKOQpBYQIL0etovhzc1ZIrhBJeVE/5XsxdbLzc+adqB
SXhtrKeROklyyxaZng3nhNWGeIjPfBwXCXD7w/vbdX5KWIBhQg35s+sN3rnY099p
7eYmNS5+C/x/iTNi5rZpOKw7tvTMZxWXANYDBbfxEhecLF0C/K2oR7pelUIVGdJD
XprHXJj12FmCsfIRzbbJ7pLUy8wVDZBVjcKqo4Z4zlVDrISo74MD/hs91SWwa86f
aAt44g1aWKjXcqXjNvgKGe1GIadp6qjQT1z5kw/mg0xp0s2Plq12Z05q0zv38kcj
ynE4o6vDZcXMBHF02jH8QknXfhvQRyq/CHaQoV9pATCvuJBLML9udRd4
=BRSB
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -46,8 +46,7 @@ For more information on the security details visit [cryptomator.org](https://cry
### Dependencies
* Java 8 (min. 8u51, we recommend to use the current version)
* [JCE unlimited strength policy files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) (needed for 256-bit keys)
* Java 10 (min. 10.0.1, we recommend to use the current version)
* Maven 3
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))

View File

@@ -4,7 +4,7 @@
<id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>tar.gz</format>
<format>zip</format>
</formats>
<fileSets>
<fileSet>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>ant-kit</artifactId>
<packaging>pom</packaging>
@@ -73,7 +73,7 @@
</executions>
</plugin>
<!-- create antkit.tar.gz: -->
<!-- create antkit.zip: -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>

View File

@@ -4,9 +4,14 @@
<!-- Define application to build -->
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
<!-- Print build environment properties -->
<target name="check-env">
<echoproperties/>
</target>
<!-- Create main application jar -->
<target name="create-jar">
<target name="create-jar" depends="check-env">
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
<fx:application refid="Cryptomator" />
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
@@ -21,27 +26,26 @@
</fx:jar>
</target>
<!-- Create Image package -->
<!-- Create Image -->
<target name="image" depends="create-jar">
<fx:deploy nativeBundles="image" outdir="antbuild" verbose="true">
<fx:application refid="Cryptomator" />
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="GPL" category="Utility"/>
<fx:platform j2se="9.0">
<fx:property name="logback.configurationFile" value="logback.xml" />
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
<fx:platform j2se="10">
<fx:property name="logback.configurationFile" value="\${antbuild.logback.configurationFile}" />
<fx:property name="cryptomator.settingsPath" value="\${antbuild.cryptomator.settingsPath}" />
<fx:property name="cryptomator.ipcPortPath" value="\${antbuild.cryptomator.ipcPortPath}" />
<fx:property name="cryptomator.keychainPath" value="\${antbuild.cryptomator.keychainPath}"/>
<fx:jvmarg value="-Xss2m"/>
<fx:jvmarg value="-Xmx512m"/>
<fx:jvmarg value="--add-modules"/>
<fx:jvmarg value="jdk.incubator.httpclient"/>
</fx:platform>
<fx:resources>
<fx:fileset dir="." type="data" includes="logback.xml" />
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
</fx:resources>
<fx:permissions elevated="false" />
<fx:preferences install="true" />
<fx:bundleArgument arg="dropinResourcesRoot" value="\${antbuild.dropinResourcesRoot}"/>
</fx:deploy>
</target>

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE xml>
<configuration scan="true" debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${user.home}/.Cryptomator/cryptomator.log</file>
<append>false</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${user.home}/.Cryptomator/cryptomator%i.log</fileNamePattern>
<minIndex>0</minIndex>
<maxIndex>9</maxIndex>
</rollingPolicy>
<triggeringPolicy class="org.cryptomator.logging.LaunchBasedTriggeringPolicy" />
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="UPGRADE_FILE" class="ch.qos.logback.core.FileAppender">
<file>${user.home}/.Cryptomator/upgrade.log</file>
<append>true</append>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.cryptomator" level="INFO" />
<logger name="org.eclipse.jetty.server.HttpChannel" level="INFO" />
<logger name="org.cryptomator.ui.model" level="INFO">
<appender-ref ref="UPGRADE_FILE" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>

View File

@@ -8,39 +8,34 @@
******************************************************************************/
package org.cryptomator.common.settings;
import java.util.function.Consumer;
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.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.function.Consumer;
public class Settings {
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_CHECK_FOR_UDPATES = true;
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
public static final boolean DEFAULT_CHECK_FOR_UDPATES = false;
public static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final String DEFAULT_GVFS_SCHEME = "dav";
public static final boolean DEFAULT_DEBUG_MODE = false;
public static final VolumeImpl DEFAULT_VOLUME_IMPL = VolumeImpl.FUSE;
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty<VolumeImpl> volumeImpl = new SimpleObjectProperty<>(DEFAULT_VOLUME_IMPL);
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
private Consumer<Settings> saveCmd;
@@ -49,12 +44,13 @@ public class Settings {
*/
Settings() {
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
askedForUpdateCheck.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged);
port.addListener(this::somethingChanged);
numTrayNotifications.addListener(this::somethingChanged);
preferredGvfsScheme.addListener(this::somethingChanged);
debugMode.addListener(this::somethingChanged);
volumeImpl.addListener(this::somethingChanged);
preferredVolumeImpl.addListener(this::somethingChanged);
}
void setSaveCmd(Consumer<Settings> saveCmd) {
@@ -77,6 +73,10 @@ public class Settings {
return directories;
}
public BooleanProperty askedForUpdateCheck() {
return askedForUpdateCheck;
}
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
@@ -97,8 +97,8 @@ public class Settings {
return debugMode;
}
public ObjectProperty<VolumeImpl> volumeImpl() {
return volumeImpl;
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
return preferredVolumeImpl;
}
}

View File

@@ -5,17 +5,16 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
@@ -28,12 +27,13 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
out.beginObject();
out.name("directories");
writeVaultSettingsArray(out, value.getDirectories());
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("port").value(value.port().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
out.name("debugMode").value(value.debugMode().get());
out.name("volumeImpl").value(value.volumeImpl().get().name());
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
out.endObject();
}
@@ -56,6 +56,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in));
break;
case "askedForUpdateCheck":
settings.askedForUpdateCheck().set(in.nextBoolean());
break;
case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean());
break;
@@ -71,8 +74,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "debugMode":
settings.debugMode().set(in.nextBoolean());
break;
case "volumeImpl":
settings.volumeImpl().set(parseNioAdapterName(in.nextString()));
case "preferredVolumeImpl":
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
break;
default:
LOG.warn("Unsupported vault setting found in JSON: " + name);
@@ -84,11 +87,11 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
return settings;
}
private VolumeImpl parseNioAdapterName(String nioAdapterName) {
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
try {
return VolumeImpl.valueOf(nioAdapterName);
} catch (IllegalArgumentException e) {
return Settings.DEFAULT_VOLUME_IMPL;
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
}
}

View File

@@ -26,7 +26,6 @@ import javafx.beans.property.StringProperty;
public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_MOUNT_AFTER_UNLOCK = true;
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
@@ -35,7 +34,6 @@ public class VaultSettings {
private final StringProperty mountName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
private final StringProperty individualMountPath = new SimpleStringProperty();
@@ -47,7 +45,7 @@ public class VaultSettings {
}
Observable[] observables() {
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount, usesIndividualMountPath, individualMountPath};
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath};
}
private void deriveMountNameFromPath(Path path) {
@@ -118,10 +116,6 @@ public class VaultSettings {
return unlockAfterStartup;
}
public BooleanProperty mountAfterUnlock() {
return mountAfterUnlock;
}
public BooleanProperty revealAfterMount() {
return revealAfterMount;
}

View File

@@ -25,7 +25,6 @@ class VaultSettingsJsonAdapter {
out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("mountAfterUnlock").value(value.mountAfterUnlock().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
//TODO: should this always be written? ( because it could contain metadata, which the user does not want to save!)
@@ -40,7 +39,6 @@ class VaultSettingsJsonAdapter {
String individualMountPath = null;
String winDriveLetter = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
@@ -63,9 +61,6 @@ class VaultSettingsJsonAdapter {
case "unlockAfterStartup":
unlockAfterStartup = in.nextBoolean();
break;
case "mountAfterUnlock":
mountAfterUnlock = in.nextBoolean();
break;
case "revealAfterMount":
revealAfterMount = in.nextBoolean();
break;
@@ -87,7 +82,6 @@ class VaultSettingsJsonAdapter {
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
vaultSettings.individualMountPath().set(individualMountPath);

View File

@@ -4,7 +4,8 @@ import java.util.Arrays;
public enum VolumeImpl {
WEBDAV("WebDAV"),
FUSE("FUSE");
FUSE("FUSE"),
DOKANY("DOKANY");
private String displayName;

View File

@@ -22,7 +22,7 @@ public class SettingsJsonAdapterTest {
+ "\"checkForUpdatesEnabled\": true,"//
+ "\"port\": 8080,"//
+ "\"numTrayNotifications\": 42,"//
+ "\"volumeImpl\": \"FUSE\"}";
+ "\"preferredVolumeImpl\": \"FUSE\"}";
Settings settings = adapter.fromJson(json);
@@ -31,7 +31,7 @@ public class SettingsJsonAdapterTest {
Assert.assertEquals(8080, settings.port().get());
Assert.assertEquals(42, settings.numTrayNotifications().get());
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
Assert.assertEquals(VolumeImpl.FUSE, settings.volumeImpl().get());
Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
}
}

View File

@@ -1 +0,0 @@
/target/

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
</parent>
<artifactId>jacoco-report</artifactId>
<name>Cryptomator Code Coverage Report</name>
<packaging>pom</packaging>
<dependencies>
<!-- all modules containing unit tests: -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>ui</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>launcher</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report-aggregate</id>
<phase>verify</phase>
<goals>
<goal>report-aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>keychain</artifactId>
<name>System Keychain Access</name>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>

View File

@@ -37,6 +37,8 @@ public class Cryptomator {
}
} catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", e);
} catch (Throwable e) {
LOG.error("Error during startup", e);
}
System.exit(0); // end remaining non-daemon threads.
}

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
@@ -25,26 +25,27 @@
<!-- dependency versions -->
<cryptomator.cryptolib.version>1.2.0</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.5.1</cryptomator.cryptofs.version>
<cryptomator.webdav.version>1.0.4</cryptomator.webdav.version>
<cryptomator.cryptofs.version>1.5.2</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
<cryptomator.fuse.version>0.1.3</cryptomator.fuse.version>
<cryptomator.fuse.version>0.1.5</cryptomator.fuse.version>
<cryptomator.dokany.version>0.1.2</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.4</cryptomator.webdav.version>
<commons-io.version>2.5</commons-io.version>
<commons-lang3.version>3.6</commons-lang3.version>
<easybind.version>1.0.3</easybind.version>
<guava.version>23.6-jre</guava.version>
<dagger.version>2.14.1</dagger.version>
<gson.version>2.8.2</gson.version>
<guava.version>25.1-jre</guava.version>
<dagger.version>2.16</dagger.version>
<gson.version>2.8.5</gson.version>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.12</junit.version>
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
<mockito.version>2.11.0</mockito.version>
<mockito.version>2.19.0</mockito.version>
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
</properties>
@@ -59,6 +60,10 @@
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
<dependencyManagement>
@@ -101,6 +106,11 @@
<artifactId>fuse-nio-adapter</artifactId>
<version>${cryptomator.fuse.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>dokany-nio-adapter</artifactId>
<version>${cryptomator.dokany.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter</artifactId>
@@ -245,9 +255,6 @@
</profile>
<profile>
<id>coverage</id>
<modules>
<module>jacoco-report</module>
</modules>
<build>
<plugins>
<plugin>
@@ -264,7 +271,7 @@
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.0.2</version>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-libs</id>
@@ -281,7 +288,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version>
<version>0.8.1</version>
<executions>
<execution>
<id>prepare-agent</id>
@@ -289,6 +296,12 @@
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
<configuration>
<excludes>
@@ -304,9 +317,9 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>9</source>
<target>9</target>
<release>9</release>
<source>10</source>
<target>10</target>
<release>10</release>
<compilerArgs>
<arg>--add-modules</arg>
<arg>jdk.incubator.httpclient</arg>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>uber-jar</artifactId>
<name>Single über jar with all dependencies</name>

View File

@@ -4,35 +4,40 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0-beta2</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
<artifactId>fuse-nio-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>fuse-nio-adapter</artifactId>
<artifactId>dokany-nio-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter</artifactId>
</dependency>
<!-- CryptoLib -->

View File

@@ -53,6 +53,7 @@ public class ExitUtil {
private final Localization localization;
private final Settings settings;
private final Optional<MacFunctions> macFunctions;
private TrayIcon trayIcon;
@Inject
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) {
@@ -82,7 +83,7 @@ public class ExitUtil {
}
private void initTrayIconExitHandler(Runnable exitCommand) {
final TrayIcon trayIcon = createTrayIcon(exitCommand);
trayIcon = createTrayIcon(exitCommand);
try {
// double clicking tray icon should open Cryptomator
if (SystemUtils.IS_OS_WINDOWS) {
@@ -118,14 +119,7 @@ public class ExitUtil {
exitItem.addActionListener(e -> exitCommand.run());
popup.add(exitItem);
final Image image;
if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) {
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_white.png"));
} else if (SystemUtils.IS_OS_MAC_OSX) {
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_black.png"));
} else {
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon.png"));
}
final Image image = getAppropriateTrayIconImage(true);
return new TrayIcon(image, localization.getString("app.name"), popup);
}
@@ -202,4 +196,23 @@ public class ExitUtil {
});
}
public void updateTrayIcon(boolean areAllVaultsLocked) {
if (trayIcon != null) {
Image image = getAppropriateTrayIconImage(areAllVaultsLocked);
trayIcon.setImage(image);
}
}
private Image getAppropriateTrayIconImage(boolean areAllVaultsLocked) {
String resourceName;
if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) {
resourceName = areAllVaultsLocked ? "/tray_icon_mac_white.png" : "/tray_icon_unlocked_mac_white.png";
} else if (SystemUtils.IS_OS_MAC_OSX) {
resourceName = areAllVaultsLocked ? "/tray_icon_mac_black.png" : "/tray_icon_unlocked_mac_black.png";
} else {
resourceName = areAllVaultsLocked ? "/tray_icon.png" : "/tray_icon_unlocked.png";
}
return Toolkit.getDefaultToolkit().getImage(getClass().getResource(resourceName));
}
}

View File

@@ -11,6 +11,8 @@ package org.cryptomator.ui;
import java.net.InetSocketAddress;
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;
import javax.inject.Named;
@@ -32,16 +34,38 @@ import org.fxmisc.easybind.EasyBind;
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
public class UiModule {
private static final int NUM_SCHEDULER_THREADS = 4;
@Provides
@Singleton
Settings provideSettings(SettingsProvider settingsProvider) {
return settingsProvider.get();
}
@Provides
@Singleton
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
Thread t = new Thread(r);
t.setName("Scheduler Thread " + threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
});
shutdownTaskScheduler.accept(executorService::shutdown);
return executorService;
}
@Provides
@Singleton
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
ExecutorService executorService = Executors.newCachedThreadPool();
final AtomicInteger threadNumber = new AtomicInteger(1);
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
t.setName("Background Thread " + threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
});
shutdownTaskScheduler.accept(executorService::shutdown);
return executorService;
}

View File

@@ -125,6 +125,7 @@ public class MainController implements ViewController {
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon);
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
autoUnlocker.unlockAllSilently();
@@ -395,7 +396,7 @@ public class MainController implements ViewController {
return;
}
if (newValue.getState() != Vault.State.LOCKED) {
this.showUnlockedView(newValue);
this.showUnlockedView(newValue, false);
} else if (!newValue.doesVaultDirectoryExist()) {
this.showNotFoundView();
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
@@ -494,22 +495,25 @@ public class MainController implements ViewController {
public void didUnlock(Vault vault) {
if (vault.equals(selectedVault.getValue())) {
this.showUnlockedView(vault);
this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue());
}
}
private void showUnlockedView(Vault vault) {
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
return viewControllerLoader.load("/fxml/unlocked.fxml");
});
private void showUnlockedView(Vault vault, boolean reveal) {
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml"));
ctrl.setVault(vault);
ctrl.setListener(this::didLock);
if (reveal) {
ctrl.revealVault(vault);
}
activeController.set(ctrl);
}
public void didLock(UnlockedController ctrl) {
unlockedVaults.remove(ctrl.getVault());
showUnlockView(UnlockController.State.UNLOCKING);
if (ctrl.getVault().getId() == selectedVault.get().getId()) {
showUnlockView(UnlockController.State.UNLOCKING);
}
activeController.get().focus();
}

View File

@@ -77,9 +77,6 @@ public class SettingsController implements ViewController {
@FXML
private ChoiceBox<String> prefGvfsScheme;
@FXML
private Label volumeLabel;
@FXML
private ChoiceBox<VolumeImpl> volume;
@@ -97,8 +94,7 @@ public class SettingsController implements ViewController {
//NIOADAPTER
volume.getItems().addAll(getSupportedAdapters());
volume.setValue(settings.volumeImpl().get());
volume.setVisible(true);
volume.setValue(settings.preferredVolumeImpl().get());
volume.setConverter(new NioAdapterImplStringConverter());
//WEBDAV
@@ -127,13 +123,13 @@ public class SettingsController implements ViewController {
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
settings.volumeImpl().bind(volume.valueProperty());
settings.preferredVolumeImpl().bind(volume.valueProperty());
settings.debugMode().bind(debugModeCheckbox.selectedProperty());
}
//TODO: how to implement this?
private VolumeImpl[] getSupportedAdapters() {
return new VolumeImpl[]{VolumeImpl.FUSE, VolumeImpl.WEBDAV};
// TODO: filter depending on supported drivers
return VolumeImpl.values();
}
@Override

View File

@@ -8,41 +8,21 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.nio.file.*;
import javax.inject.Inject;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.scene.layout.HBox;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.frontend.webdav.ServerLifecycleException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.WindowsDriveLetters;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutorService;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
@@ -59,8 +39,28 @@ import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.frontend.webdav.ServerLifecycleException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.WindowsDriveLetters;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.util.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnlockController implements ViewController {
@@ -73,23 +73,23 @@ public class UnlockController implements ViewController {
private final Application app;
private final Localization localization;
private final AsyncTaskService asyncTaskService;
private final WindowsDriveLetters driveLetters;
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
private final Optional<KeychainAccess> keychainAccess;
private final Settings settings;
private final ExecutorService executor;
private Vault vault;
private Optional<UnlockListener> listener = Optional.empty();
private Subscription vaultSubs = Subscription.EMPTY;
@Inject
public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings) {
public UnlockController(Application app, Localization localization, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings, ExecutorService executor) {
this.app = app;
this.localization = localization;
this.asyncTaskService = asyncTaskService;
this.driveLetters = driveLetters;
this.keychainAccess = keychainAccess;
this.settings = settings;
this.executor = executor;
}
@FXML
@@ -107,9 +107,6 @@ public class UnlockController implements ViewController {
@FXML
private CheckBox savePassword;
@FXML
private CheckBox mountAfterUnlock;
@FXML
private TextField mountName;
@@ -159,8 +156,6 @@ public class UnlockController implements ViewController {
public void initialize() {
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
mountName.disableProperty().bind(mountAfterUnlock.selectedProperty().not());
revealAfterMount.disableProperty().bind(mountAfterUnlock.selectedProperty().not());
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange);
savePassword.setDisable(!keychainAccess.isPresent());
@@ -186,7 +181,7 @@ public class UnlockController implements ViewController {
winDriveLetterLabel.setManaged(false);
winDriveLetter.setVisible(false);
winDriveLetter.setManaged(false);
if (VolumeImpl.WEBDAV.equals(settings.volumeImpl().get())) {
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
useOwnMountPath.setVisible(false);
useOwnMountPath.setManaged(false);
mountPathLabel.setManaged(false);
@@ -250,12 +245,10 @@ public class UnlockController implements ViewController {
}
VaultSettings vaultSettings = vault.getVaultSettings();
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
mountAfterUnlock.setSelected(vaultSettings.mountAfterUnlock().get());
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
useOwnMountPath.setSelected(vaultSettings.usesIndividualMountPath().get());
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), vaultSettings.mountAfterUnlock()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
@@ -430,7 +423,7 @@ public class UnlockController implements ViewController {
progressIndicator.setVisible(true);
CharSequence password = passwordField.getCharacters();
asyncTaskService.asyncTaskOf(() -> {
Tasks.create(() -> {
vault.unlock(password);
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
@@ -457,16 +450,16 @@ public class UnlockController implements ViewController {
}).onError(ServerLifecycleException.class, e -> {
LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
}).onError(IOException.class, e -> {
LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
}).onError(Exception.class, e -> {
LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
}).andFinally(() -> {
if (!savePassword.isSelected()) {
passwordField.swipe();
}
advancedOptions.setDisable(false);
progressIndicator.setVisible(false);
}).run();
}).runOnce(executor);
}
/* callback */

View File

@@ -8,29 +8,13 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import static java.lang.String.format;
import java.io.IOException;
import java.util.Optional;
import javax.inject.Inject;
import org.cryptomator.frontend.webdav.ServerLifecycleException;
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.Runnables;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
@@ -46,13 +30,19 @@ import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.VBox;
import javafx.stage.PopupWindow.AnchorLocation;
import javafx.util.Duration;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.util.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.String.format;
public class UnlockedController implements ViewController {
@@ -62,9 +52,8 @@ public class UnlockedController implements ViewController {
private static final double IO_SAMPLING_INTERVAL = 0.25;
private final Localization localization;
private final AsyncTaskService asyncTaskService;
private final ExecutorService executor;
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private final ObjectExpression<Vault.State> vaultState = ObjectExpression.objectExpression(EasyBind.select(vault).selectObject(Vault::stateProperty));
private Optional<LockListener> listener = Optional.empty();
private Timeline ioAnimation;
@@ -83,30 +72,17 @@ public class UnlockedController implements ViewController {
@FXML
private ContextMenu moreOptionsMenu;
@FXML
private MenuItem mountVaultMenuItem;
@FXML
private MenuItem unmountVaultMenuItem;
@FXML
private MenuItem revealVaultMenuItem;
@FXML
private VBox root;
@Inject
public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) {
public UnlockedController(Localization localization, ExecutorService executor) {
this.localization = localization;
this.asyncTaskService = asyncTaskService;
this.executor = executor;
}
@Override
public void initialize() {
mountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.UNLOCKED).not()); // enable when unlocked
unmountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted
revealVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted
EasyBind.subscribe(vault, this::vaultChanged);
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
}
@@ -121,10 +97,6 @@ public class UnlockedController implements ViewController {
return;
}
if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) {
mountVault(newVault);
}
// (re)start throughput statistics:
stopIoSampling();
startIoSampling();
@@ -132,75 +104,22 @@ public class UnlockedController implements ViewController {
@FXML
private void didClickLockVault(ActionEvent event) {
regularUnmountVault(this::lockVault);
regularLockVault(this::lockVaultSucceeded);
}
private void lockVault() {
try {
vault.get().lock();
} catch (ServerLifecycleException | IOException e) {
LOG.error("Lock failed", e);
}
private void lockVaultSucceeded() {
listener.ifPresent(listener -> listener.didLock(this));
}
@FXML
private void didClickMoreOptions(ActionEvent event) {
if (moreOptionsMenu.isShowing()) {
moreOptionsMenu.hide();
} else {
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
}
}
@FXML
public void didClickMountVault(ActionEvent event) {
mountVault(vault.get());
}
private void mountVault(Vault vault) {
asyncTaskService.asyncTaskOf(() -> {
vault.mount();
}).onSuccess(() -> {
LOG.trace("Mount succeeded.");
messageLabel.setText(null);
if (vault.getVaultSettings().revealAfterMount().get()) {
revealVault(vault);
}
}).onError(CommandFailedException.class, e -> {
LOG.error("Mount failed.", e);
// TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
}).run();
}
@FXML
public void didClickUnmountVault(ActionEvent event) {
regularUnmountVault(Runnables.doNothing());
}
private void regularUnmountVault(Runnable onSuccess) {
asyncTaskService.asyncTaskOf(() -> {
vault.get().unmount();
private void regularLockVault(Runnable onSuccess) {
Tasks.create(() -> {
vault.get().lock(false);
}).onSuccess(() -> {
LOG.trace("Regular unmount succeeded.");
onSuccess.run();
}).onError(Exception.class, e -> {
onRegularUnmountVaultFailed(e, onSuccess);
}).run();
}
private void forcedUnmountVault(Runnable onSuccess) {
asyncTaskService.asyncTaskOf(() -> {
vault.get().unmountForced();
}).onSuccess(() -> {
LOG.trace("Forced unmount succeeded.");
onSuccess.run();
}).onError(Exception.class, e -> {
LOG.error("Forced unmount failed.", e);
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}).run();
}).runOnce(executor);
}
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
@@ -214,7 +133,7 @@ public class UnlockedController implements ViewController {
Optional<ButtonType> choice = confirmDialog.showAndWait();
if (ButtonType.YES.equals(choice.get())) {
forcedUnmountVault(onSuccess);
forcedLockVault(onSuccess);
} else {
LOG.trace("Unmount cancelled.", e);
}
@@ -224,29 +143,43 @@ public class UnlockedController implements ViewController {
}
}
private void forcedLockVault(Runnable onSuccess) {
Tasks.create(() -> {
vault.get().lock(true);
}).onSuccess(() -> {
LOG.trace("Forced unmount succeeded.");
onSuccess.run();
}).onError(Exception.class, e -> {
LOG.error("Forced unmount failed.", e);
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}).runOnce(executor);
}
@FXML
private void didClickMoreOptions(ActionEvent event) {
if (moreOptionsMenu.isShowing()) {
moreOptionsMenu.hide();
} else {
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
}
}
@FXML
private void didClickRevealVault(ActionEvent event) {
revealVault(vault.get());
}
private void revealVault(Vault vault) {
asyncTaskService.asyncTaskOf(() -> {
void revealVault(Vault vault) {
Tasks.create(() -> {
vault.reveal();
}).onSuccess(() -> {
LOG.trace("Reveal succeeded.");
messageLabel.setText(null);
}).onError(CommandFailedException.class, e -> {
}).onError(Exception.class, e -> {
LOG.error("Reveal failed.", e);
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
}).run();
}
@FXML
private void didClickCopyUrl(ActionEvent event) {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putUrl(vault.get().getFilesystemRootUrl());
clipboardContent.putString(vault.get().getFilesystemRootUrl());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}).runOnce(executor);
}
// ****************************************

View File

@@ -5,19 +5,10 @@
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.AsyncTaskService;
import org.fxmisc.easybind.EasyBind;
import java.util.concurrent.ExecutorService;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
@@ -30,19 +21,26 @@ import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.Tasks;
import org.fxmisc.easybind.EasyBind;
public class UpgradeController implements ViewController {
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
private final UpgradeStrategies strategies;
private final AsyncTaskService asyncTaskService;
private final ExecutorService executor;
private Optional<UpgradeListener> listener = Optional.empty();
private Vault vault;
@Inject
public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) {
public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
this.strategies = strategies;
this.asyncTaskService = asyncTaskService;
this.executor = executor;
}
@FXML
@@ -122,8 +120,8 @@ public class UpgradeController implements ViewController {
private void upgrade(UpgradeStrategy instruction) {
passwordField.setDisable(true);
progressIndicator.setVisible(true);
asyncTaskService //
.asyncTaskOf(() -> {
Tasks //
.create(() -> {
if (!instruction.isApplicable(vault)) {
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
}
@@ -137,7 +135,7 @@ public class UpgradeController implements ViewController {
progressIndicator.setVisible(false);
passwordField.setDisable(false);
passwordField.swipe();
}).run();
}).runOnce(executor);
}
private void showNextUpgrade() {

View File

@@ -2,24 +2,27 @@
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -30,20 +33,20 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.Tasks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
@Singleton
public class WelcomeController implements ViewController {
@@ -54,17 +57,17 @@ public class WelcomeController implements ViewController {
private final Localization localization;
private final Settings settings;
private final Comparator<String> semVerComparator;
private final AsyncTaskService asyncTaskService;
private final ScheduledExecutorService executor;
@Inject
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
AsyncTaskService asyncTaskService) {
ScheduledExecutorService executor) {
this.app = app;
this.applicationVersion = applicationVersion;
this.localization = localization;
this.settings = settings;
this.semVerComparator = semVerComparator;
this.asyncTaskService = asyncTaskService;
this.executor = executor;
}
@FXML
@@ -86,6 +89,8 @@ public class WelcomeController implements ViewController {
public void initialize(URL location, ResourceBundle resources) {
if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false);
} else if (!settings.askedForUpdateCheck().get()) {
this.askForUpdateCheck();
} else if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
@@ -104,34 +109,63 @@ public class WelcomeController implements ViewController {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
}
private void askForUpdateCheck() {
Tasks.create(() -> {}).onSuccess(() -> {
Optional<ButtonType> result = buildYesNoDialog(
localization.getString("welcome.askForUpdateCheck.dialog.title"),
localization.getString("welcome.askForUpdateCheck.dialog.header"),
localization.getString("welcome.askForUpdateCheck.dialog.content"),
ButtonType.YES).showAndWait();
if (result.isPresent()) {
settings.askedForUpdateCheck().set(true);
settings.checkForUpdates().set(result.get().equals(ButtonType.YES));
}
if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
}).scheduleOnce(executor, 1, TimeUnit.SECONDS);
}
private void checkForUpdates() {
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
checkForUpdatesIndicator.setVisible(true);
asyncTaskService.asyncTaskOf(() -> {
Tasks.create(() -> {
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(new URI("https://api.cryptomator.org/updates/latestVersion.json"))
.header("User-Agent", userAgent)
.timeout(Duration.ofSeconds(5))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8));
if (response.statusCode() == 200) {
URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("User-Agent", userAgent);
conn.connect();
try {
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
return Optional.<byte[]>empty();
}
try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
return Optional.of(out.toByteArray());
}
} finally {
conn.disconnect();
}
}).onSuccess(response -> {
response.ifPresent(bytes -> {
Gson gson = new GsonBuilder().setLenient().create();
Map<String, String> map = gson.fromJson(response.body(), new TypeToken<Map<String, String>>() {
String json = new String(bytes, StandardCharsets.UTF_8);
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
}.getType());
if (map != null) {
this.compareVersions(map);
}
}
});
}).onError(Exception.class, e -> {
LOG.warn("Error checking for updates", e);
}).andFinally(() -> {
checkForUpdatesStatus.setText("");
checkForUpdatesIndicator.setVisible(false);
}).run();
}).runOnce(executor);
}
private void compareVersions(final Map<String, String> latestVersions) {
assert Platform.isFxApplicationThread();
final String latestVersion;
if (SystemUtils.IS_OS_MAC_OSX) {
latestVersion = latestVersions.get("mac");
@@ -147,11 +181,9 @@ public class WelcomeController implements ViewController {
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
Platform.runLater(() -> {
this.updateLink.setText(msg);
this.updateLink.setVisible(true);
this.updateLink.setDisable(false);
});
this.updateLink.setText(msg);
this.updateLink.setVisible(true);
this.updateLink.setDisable(false);
}
}

View File

@@ -68,9 +68,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
}
switch (state) {
case UNLOCKED:
case MOUNTED:
case MOUNTING:
case UNMOUNTING:
case PROCESSING:
return "\uf09c";
case LOCKED:
default:
@@ -84,9 +82,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
}
switch (state) {
case UNLOCKED:
case MOUNTED:
case MOUNTING:
case UNMOUNTING:
case PROCESSING:
return UNLOCKED_ICON_COLOR;
case LOCKED:
default:

View File

@@ -73,33 +73,21 @@ public class AutoUnlocker {
}
try {
vault.unlock(CharBuffer.wrap(storedPw));
mountSilently(vault);
} catch (IOException | CryptoException e) {
revealSilently(vault);
} catch (IOException | CryptoException | Volume.VolumeException e) {
LOG.error("Auto unlock failed.", e);
} finally {
Arrays.fill(storedPw, ' ');
}
}
private void mountSilently(Vault unlockedVault) {
if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) {
return;
}
try {
unlockedVault.mount();
revealSilently(unlockedVault);
} catch (CommandFailedException e) {
LOG.error("Auto unlock succeded, but mounting the drive failed.", e);
}
}
private void revealSilently(Vault mountedVault) {
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
return;
}
try {
mountedVault.reveal();
} catch (CommandFailedException e) {
} catch (Volume.VolumeException e) {
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
}
}

View File

@@ -1,17 +0,0 @@
package org.cryptomator.ui.model;
public class CommandFailedException extends Exception {
public CommandFailedException(String message) {
super(message);
}
public CommandFailedException(Throwable cause) {
super(cause);
}
public CommandFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,72 @@
package org.cryptomator.ui.model;
import javax.inject.Inject;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.dokany.Mount;
import org.cryptomator.frontend.dokany.MountFactory;
public class DokanyVolume implements Volume {
private static final String FS_TYPE_NAME = "Cryptomator File System";
private final VaultSettings vaultSettings;
private final MountFactory mountFactory;
private final WindowsDriveLetters windowsDriveLetters;
private Mount mount;
@Inject
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
this.vaultSettings = vaultSettings;
this.mountFactory = new MountFactory(executorService);
this.windowsDriveLetters = windowsDriveLetters;
}
@Override
public boolean isSupported() {
return MountFactory.isApplicable();
}
//TODO: Drive letter 'A' as mount point is invalid in dokany. maybe we should do already here something against it
@Override
public void mount(CryptoFileSystem fs) throws VolumeException {
char driveLetter;
if (!vaultSettings.winDriveLetter().getValueSafe().equals("")) {
driveLetter = vaultSettings.winDriveLetter().get().charAt(0);
} else {
//auto assign drive letter
//TODO: can we assume the we have at least one free drive letter?
//this is a temporary fix for 'A' being an invalid drive letter
if(!windowsDriveLetters.getAvailableDriveLetters().isEmpty()){
Iterator<Character> winDriveLetterIt = windowsDriveLetters.getAvailableDriveLetters().iterator();
do{
driveLetter = winDriveLetterIt.next();
}while (winDriveLetterIt.hasNext() && driveLetter == 65);
// if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
// driveLetter = windowsDriveLetters.getAvailableDriveLetters().iterator().next();
} else {
throw new VolumeException("No free drive letter available.");
}
}
String mountName = vaultSettings.mountName().get();
this.mount = mountFactory.mount(fs.getPath("/"), driveLetter, mountName, FS_TYPE_NAME);
}
@Override
public void reveal() throws VolumeException {
boolean success = mount.reveal();
if (!success) {
throw new VolumeException("Reveal failed.");
}
}
@Override
public void unmount() {
mount.close();
}
}

View File

@@ -2,6 +2,7 @@ package org.cryptomator.ui.model;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -11,6 +12,7 @@ import javax.inject.Inject;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
@@ -18,7 +20,6 @@ import org.cryptomator.frontend.fuse.mount.Mount;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@VaultModule.PerVault
public class FuseVolume implements Volume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
@@ -30,34 +31,21 @@ public class FuseVolume implements Volume {
private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
private final VaultSettings vaultSettings;
private final WindowsDriveLetters windowsDriveLetters;
private Mount fuseMnt;
private CryptoFileSystem cfs;
private Path mountPath;
private boolean extraDirCreated;
@Inject
public FuseVolume(VaultSettings vaultSettings, WindowsDriveLetters windowsDriveLetters) {
public FuseVolume(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings;
this.windowsDriveLetters = windowsDriveLetters;
this.extraDirCreated = false;
}
@Override
public void prepare(CryptoFileSystem fs) throws IOException, FuseNotSupportedException {
this.cfs = fs;
public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
String mountPath;
if (SystemUtils.IS_OS_WINDOWS) {
//windows case
if (vaultSettings.winDriveLetter().get() != null) {
// specific drive letter selected
mountPath = vaultSettings.winDriveLetter().get() + ":\\";
} else {
// auto assign drive letter
mountPath = windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\";
}
} else if (vaultSettings.usesIndividualMountPath().get()) {
if (vaultSettings.usesIndividualMountPath().get()) {
//specific path given
mountPath = vaultSettings.individualMountPath().get();
} else {
@@ -66,57 +54,60 @@ public class FuseVolume implements Volume {
extraDirCreated = true;
}
this.mountPath = Paths.get(mountPath).toAbsolutePath();
mount(fs.getPath("/"));
}
private String createDirIfNotExist(String prefix, String dirName) throws IOException {
Path p = Paths.get(prefix, dirName + vaultSettings.getId());
if (Files.isDirectory(p) && !Files.newDirectoryStream(p).iterator().hasNext()) {
throw new DirectoryNotEmptyException("Mount point is not empty.");
if (Files.isDirectory(p)) {
try(DirectoryStream<Path> emptyCheck = Files.newDirectoryStream(p)){
if(emptyCheck.iterator().hasNext()){
throw new DirectoryNotEmptyException("Mount point is not empty.");
}
else {
LOG.info("Directory already exists and is empty. Using it as mount point.");
return p.toString();
}
}
} else {
Files.createDirectory(p);
return p.toString();
}
}
@Override
public void mount() throws CommandFailedException {
private void mount(Path root) throws VolumeException {
try {
EnvironmentVariables envVars = EnvironmentVariables.create()
.withMountName(vaultSettings.mountName().getValue())
.withMountPath(mountPath)
.build();
this.fuseMnt = FuseMountFactory.getMounter().mount(cfs.getPath("/"), envVars);
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) {
throw new CommandFailedException("Unable to mount Filesystem", e);
this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars);
} catch (CommandFailedException e) {
throw new VolumeException("Unable to mount Filesystem", e);
}
}
@Override
public void reveal() throws CommandFailedException {
public void reveal() throws VolumeException {
try {
fuseMnt.revealInFileManager();
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) {
} catch (CommandFailedException e) {
LOG.info("Revealing the vault in file manger failed: " + e.getMessage());
throw new CommandFailedException(e);
throw new VolumeException(e);
}
}
@Override
public synchronized void unmount() throws CommandFailedException {
public synchronized void unmount() throws VolumeException {
try {
fuseMnt.close();
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) {
throw new CommandFailedException(e);
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
cleanup();
}
@Override
public synchronized void unmountForced() throws CommandFailedException {
unmount();
}
@Override
public void stop() {
private void cleanup() {
if (extraDirCreated) {
try {
Files.delete(mountPath);
@@ -126,19 +117,9 @@ public class FuseVolume implements Volume {
}
}
@Override
public String getMountUri() {
return "";
}
@Override
public boolean isSupported() {
return FuseMountFactory.isFuseSupported();
}
@Override
public boolean supportsForcedUnmount() {
return false;
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
}
}

View File

@@ -19,16 +19,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import javax.inject.Inject;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.model.VaultModule.PerVault;
import javax.inject.Provider;
import javafx.application.Platform;
import javafx.beans.Observable;
@@ -39,6 +30,15 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.model.VaultModule.PerVault;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,20 +53,21 @@ public class Vault {
private final Settings settings;
private final VaultSettings vaultSettings;
private final Provider<Volume> volumeProvider;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
private Volume volume;
public enum State {
LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING
LOCKED, PROCESSING, UNLOCKED
}
@Inject
Vault(Settings settings, VaultSettings vaultSettings, Volume volume) {
Vault(Settings settings, VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
this.settings = settings;
this.vaultSettings = vaultSettings;
this.volume = volume;
this.volumeProvider = volumeProvider;
}
// ******************************************************************************
@@ -98,51 +99,37 @@ public class Vault {
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException {
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
volume.prepare(fs);
Platform.runLater(() -> {
state.set(State.UNLOCKED);
});
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
Platform.runLater(() -> state.set(State.PROCESSING));
try {
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
volume = volumeProvider.get();
volume.mount(fs);
Platform.runLater(() -> {
state.set(State.UNLOCKED);
});
} catch (Exception e) {
Platform.runLater(() -> state.set(State.LOCKED));
throw e;
}
}
public synchronized void mount() throws CommandFailedException {
public synchronized void lock(boolean forced) throws Volume.VolumeException {
Platform.runLater(() -> {
state.set(State.MOUNTING);
});
volume.mount();
Platform.runLater(() -> {
state.set(State.MOUNTED);
});
}
public synchronized void unmountForced() throws CommandFailedException {
unmount(true);
}
public synchronized void unmount() throws CommandFailedException {
unmount(false);
}
private synchronized void unmount(boolean forced) throws CommandFailedException {
Platform.runLater(() -> {
state.set(State.UNMOUNTING);
state.set(State.PROCESSING);
});
if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced();
} else {
volume.unmount();
}
Platform.runLater(() -> {
state.set(State.UNLOCKED);
});
}
public synchronized void lock() throws IOException {
volume.stop();
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
if (fs != null) {
fs.close();
try {
fs.close();
} catch (IOException e) {
LOG.error("Error closing file system.", e);
}
}
Platform.runLater(() -> {
state.set(State.LOCKED);
@@ -154,26 +141,21 @@ public class Vault {
*/
public void prepareForShutdown() {
try {
unmount();
} catch (CommandFailedException e) {
lock(false);
} catch (Volume.VolumeException e) {
if (volume.supportsForcedUnmount()) {
try {
unmountForced();
} catch (CommandFailedException e1) {
LOG.warn("Failed to force unmount vault.");
lock(true);
} catch (Volume.VolumeException e1) {
LOG.warn("Failed to force lock vault.", e1);
}
} else {
LOG.warn("Failed to gracefully unmount vault.");
LOG.warn("Failed to gracefully lock vault.", e);
}
}
try {
lock();
} catch (Exception e) {
LOG.warn("Failed to lock vault.");
}
}
public void reveal() throws CommandFailedException {
public void reveal() throws Volume.VolumeException {
volume.reveal();
}
@@ -289,10 +271,6 @@ public class Vault {
}
}
public String getFilesystemRootUrl() {
return volume.getMountUri();
}
public String getId() {
return vaultSettings.getId();
}

View File

@@ -18,10 +18,13 @@ import org.cryptomator.common.settings.VaultSettings;
import dagger.Module;
import dagger.Provides;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Module
public class VaultModule {
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
private final VaultSettings vaultSettings;
public VaultModule(VaultSettings vaultSettings) {
@@ -42,21 +45,18 @@ public class VaultModule {
}
@Provides
@PerVault
public Volume provideNioAdpater(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume) {
VolumeImpl impl = settings.volumeImpl().get();
switch (impl) {
case FUSE:
if (fuseVolume.isSupported()) {
return fuseVolume;
} else {
settings.volumeImpl().set(VolumeImpl.WEBDAV);
// fallthrough to WEBDAV
}
case WEBDAV:
return webDavVolume;
default:
throw new IllegalStateException("Unsupported NioAdapter: " + impl);
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
return dokanyVolume;
} else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
return fuseVolume;
} else {
if (VolumeImpl.WEBDAV != preferredImpl) {
LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
}
assert webDavVolume.isSupported();
return webDavVolume;
}
}

View File

@@ -9,30 +9,50 @@ import java.io.IOException;
*/
public interface Volume {
void prepare(CryptoFileSystem fs) throws IOException;
/**
* Checks in constant time whether this volume type is supported on the system running Cryptomator.
* @return true if this volume can be mounted
*/
boolean isSupported();
void mount() throws CommandFailedException;
/**
*
* @param fs
* @throws IOException
*/
void mount(CryptoFileSystem fs) throws IOException, VolumeException;
default void reveal() throws CommandFailedException {
throw new CommandFailedException("Not implemented.");
}
void reveal() throws VolumeException;
void unmount() throws CommandFailedException;
void unmount() throws VolumeException;
default void unmountForced() throws CommandFailedException {
throw new CommandFailedException("Operation not supported.");
}
void stop();
String getMountUri();
default boolean isSupported() {
return false;
}
// optional forced unmounting:
default boolean supportsForcedUnmount() {
return false;
}
default void unmountForced() throws VolumeException {
throw new VolumeException("Operation not supported.");
}
/**
* Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
*/
class VolumeException extends Exception {
public VolumeException(String message) {
super(message);
}
public VolumeException(Throwable cause) {
super(cause);
}
public VolumeException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -15,7 +15,6 @@ import javax.inject.Provider;
import java.net.InetAddress;
import java.net.UnknownHostException;
@VaultModule.PerVault
public class WebDavVolume implements Volume {
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
@@ -36,7 +35,7 @@ public class WebDavVolume implements Volume {
}
@Override
public void prepare(CryptoFileSystem fs) {
public void mount(CryptoFileSystem fs) throws VolumeException {
if (server == null) {
server = serverProvider.get();
}
@@ -45,10 +44,10 @@ public class WebDavVolume implements Volume {
}
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
servlet.start();
mount();
}
@Override
public void mount() throws CommandFailedException {
private void mount() throws VolumeException {
if (servlet == null) {
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
}
@@ -61,32 +60,38 @@ public class WebDavVolume implements Volume {
this.mount = servlet.mount(mountParams); // might block this thread for a while
} catch (Mounter.CommandFailedException e) {
e.printStackTrace();
throw new CommandFailedException(e);
throw new VolumeException(e);
}
}
@Override
public void reveal() throws CommandFailedException {
public void reveal() throws VolumeException {
try {
mount.reveal();
} catch (Mounter.CommandFailedException e) {
e.printStackTrace();
throw new CommandFailedException(e);
throw new VolumeException(e);
}
}
@Override
public synchronized void unmount() throws CommandFailedException {
public synchronized void unmount() throws VolumeException {
try {
mount.unmount();
} catch (Mounter.CommandFailedException e) {
throw new CommandFailedException(e);
throw new VolumeException(e);
}
cleanup();
}
@Override
public synchronized void unmountForced() {
mount.forced();
public synchronized void unmountForced() throws VolumeException {
try {
mount.forced().orElseThrow(IllegalStateException::new).unmount();
} catch (Mounter.CommandFailedException e) {
throw new VolumeException(e);
}
cleanup();
}
private String getLocalhostAliasOrNull() {
@@ -102,28 +107,19 @@ public class WebDavVolume implements Volume {
}
}
@Override
public void stop() {
private void cleanup() {
if (servlet != null) {
servlet.stop();
}
}
public synchronized String getMountUri() {
return servlet.getServletRootUri().toString() + "/";
}
/**
* TODO: what to check wether it is implemented?
*
* @return
*/
@Override
public boolean isSupported() {
return true;
}
@Override
public boolean supportsForcedUnmount() {
return mount != null && mount.forced().isPresent();
}

View File

@@ -1,219 +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.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.common.ConsumerThrowingException;
import org.cryptomator.common.RunnableThrowingException;
import org.cryptomator.common.SupplierThrowingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
@Singleton
public class AsyncTaskService {
private static final Logger LOG = LoggerFactory.getLogger(AsyncTaskService.class);
private final ExecutorService executor;
@Inject
public AsyncTaskService(ExecutorService executor) {
this.executor = executor;
}
/**
* Creates a new async task
*
* @param task Tasks to be invoked in a background thread.
* @return The async task
*/
public AsyncTaskWithoutSuccessHandler<Void> asyncTaskOf(RunnableThrowingException<?> task) {
return new AsyncTaskImpl<>(() -> {
task.run();
return null;
});
}
/**
* Creates a new async task
*
* @param task Tasks to be invoked in a background thread.
* @return The async task
*/
public <ResultType> AsyncTaskWithoutSuccessHandler<ResultType> asyncTaskOf(SupplierThrowingException<ResultType, ?> task) {
return new AsyncTaskImpl<>(task);
}
private class AsyncTaskImpl<ResultType> implements AsyncTaskWithoutSuccessHandler<ResultType> {
private final SupplierThrowingException<ResultType, ?> task;
private ConsumerThrowingException<ResultType, ?> successHandler = value -> {
};
private final List<ErrorHandler<Throwable>> errorHandlers = new ArrayList<>();
private RunnableThrowingException<?> finallyHandler = () -> {
};
public AsyncTaskImpl(SupplierThrowingException<ResultType, ?> task) {
this.task = task;
}
@Override
public AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler) {
successHandler = handler;
return this;
}
@Override
public AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler) {
return onSuccess(result -> handler.run());
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler) {
errorHandlers.add((ErrorHandler) new ErrorHandler<>(type, handler));
return this;
}
@Override
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler) {
return onError(type, error -> handler.run());
}
@Override
public AsyncTask andFinally(RunnableThrowingException<?> handler) {
finallyHandler = handler;
return this;
}
@Override
public void run() {
errorHandlers.add(ErrorHandler.LOGGING_HANDLER);
executor.execute(() -> logExceptions(() -> {
try {
ResultType result = task.get();
Platform.runLater(() -> {
try {
successHandler.accept(result);
} catch (Throwable e) {
LOG.error("Uncaught exception", e);
}
});
} catch (Throwable e) {
ErrorHandler<Throwable> errorHandler = errorHandlerFor(e);
Platform.runLater(toRunnableLoggingException(() -> errorHandler.accept(e)));
} finally {
Platform.runLater(toRunnableLoggingException(finallyHandler));
}
}));
}
private ErrorHandler<Throwable> errorHandlerFor(Throwable e) {
return errorHandlers.stream().filter(handler -> handler.handles(e)).findFirst().get();
}
}
private static Runnable toRunnableLoggingException(RunnableThrowingException<?> delegate) {
return () -> logExceptions(delegate);
}
private static void logExceptions(RunnableThrowingException<?> delegate) {
try {
delegate.run();
} catch (Throwable e) {
LOG.error("Uncaught exception", e);
}
}
private static class ErrorHandler<ErrorType> implements ConsumerThrowingException<ErrorType, Throwable> {
public static final ErrorHandler<Throwable> LOGGING_HANDLER = new ErrorHandler<Throwable>(Throwable.class, error -> {
LOG.error("Uncaught exception", error);
});
private final Class<ErrorType> type;
private final ConsumerThrowingException<ErrorType, ?> delegate;
public ErrorHandler(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> delegate) {
this.type = type;
this.delegate = delegate;
}
public boolean handles(Throwable error) {
return type.isInstance(error);
}
@Override
public void accept(ErrorType error) throws Throwable {
delegate.accept(error);
}
}
public interface AsyncTaskWithoutSuccessHandler<ResultType> extends AsyncTaskWithoutErrorHandler {
/**
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler);
/**
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler);
}
public interface AsyncTaskWithoutErrorHandler extends AsyncTaskWithoutFinallyHandler {
/**
* @param type Exception type to catch
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler);
/**
* @param type Exception type to catch
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler);
}
public interface AsyncTaskWithoutFinallyHandler extends AsyncTask {
/**
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
AsyncTask andFinally(RunnableThrowingException<?> handler);
}
public interface AsyncTask extends Runnable {
/**
* Starts the async task.
*/
@Override
void run();
}
}

View File

@@ -28,6 +28,8 @@ import javafx.scene.paint.Color;
@Singleton
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 final Zxcvbn zxcvbn;
private final List<String> sanitizedInputs;
private final Localization localization;
@@ -43,10 +45,9 @@ public class PasswordStrengthUtil {
public int computeRate(String password) {
if (Strings.isNullOrEmpty(password)) {
return -1;
} else if (password.length() > 100) {
return 4; // assume this is strong. zxcvbn memory and runtime depends vastly on the password length
} else {
return zxcvbn.measure(password, sanitizedInputs).getScore();
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore();
}
}

View File

@@ -0,0 +1,192 @@
/*******************************************************************************
* Copyright (c) 2018 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.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tasks {
private static final Logger LOG = LoggerFactory.getLogger(Tasks.class);
public static <T> TaskBuilder<T> create(Callable<T> callable) {
return new TaskBuilder<>(callable);
}
public static TaskBuilder<Void> create(VoidCallable callable) {
return create(() -> {
callable.call();
return null;
});
}
public static class TaskBuilder<T> {
private final Callable<T> callable;
private Consumer<T> successHandler = x -> {
};
private List<ErrorHandler<?>> errorHandlers = new ArrayList<>();
private Runnable finallyHandler = () -> {};
TaskBuilder(Callable<T> callable) {
this.callable = callable;
}
public TaskBuilder<T> onSuccess(Runnable successHandler) {
this.successHandler = x -> {
successHandler.run();
};
return this;
}
public TaskBuilder<T> onSuccess(Consumer<T> successHandler) {
this.successHandler = successHandler;
return this;
}
public <E extends Throwable> TaskBuilder<T> onError(Class<E> type, Consumer<E> exceptionHandler) {
errorHandlers.add(new ErrorHandler<>(type, exceptionHandler));
return this;
}
public TaskBuilder<T> andFinally(Runnable finallyHandler) {
this.finallyHandler = finallyHandler;
return this;
}
private Task<T> build() {
return new TaskImpl<>(callable, successHandler, errorHandlers, finallyHandler);
}
public Task<T> runOnce(ExecutorService executorService) {
Task<T> task = build();
executorService.submit(task);
return task;
}
public Task<T> scheduleOnce(ScheduledExecutorService executorService, long delay, TimeUnit unit) {
Task<T> task = build();
executorService.schedule(task, delay, unit);
return task;
}
public ScheduledService<T> schedulePeriodically(ExecutorService executorService, Duration initialDelay, Duration period) {
ScheduledService<T> service = new RestartingService<>(this::build);
service.setExecutor(executorService);
service.setDelay(initialDelay);
service.setPeriod(period);
Platform.runLater(service::start);
return service;
}
}
private static class ErrorHandler<ErrorType extends Throwable> {
private final Class<ErrorType> type;
private final Consumer<ErrorType> errorHandler;
public ErrorHandler(Class<ErrorType> type, Consumer<ErrorType> errorHandler) {
this.type = type;
this.errorHandler = errorHandler;
}
public boolean handles(Throwable error) {
return type.isInstance(error);
}
public void handle(Throwable error) throws ClassCastException {
ErrorType typedError = type.cast(error);
errorHandler.accept(typedError);
}
}
/**
* Adapter between java.util.function.* and javafx.concurrent.*
*/
private static class TaskImpl<T> extends Task<T> {
private final Callable<T> callable;
private final Consumer<T> successHandler;
private List<ErrorHandler<?>> errorHandlers;
private final Runnable finallyHandler;
TaskImpl(Callable<T> callable, Consumer<T> successHandler, List<ErrorHandler<?>> errorHandlers, Runnable finallyHandler) {
this.callable = callable;
this.successHandler = successHandler;
this.errorHandlers = errorHandlers;
this.finallyHandler = finallyHandler;
}
@Override
protected T call() throws Exception {
return callable.call();
}
@Override
protected void succeeded() {
try {
successHandler.accept(getValue());
} finally {
finallyHandler.run();
}
}
@Override
protected void failed() {
try {
Throwable exception = getException();
errorHandlers.stream().filter(handler -> handler.handles(exception)).findFirst().ifPresentOrElse(exceptionHandler -> {
exceptionHandler.handle(exception);
}, () -> {
LOG.error("Unhandled exception", exception);
});
} finally {
finallyHandler.run();
}
}
}
/**
* A service that keeps executing the next task long as tasks are succeeding.
*/
private static class RestartingService<T> extends ScheduledService<T> {
private final Supplier<Task<T>> taskFactory;
RestartingService(Supplier<Task<T>> taskFactory) {
this.taskFactory = taskFactory;
}
@Override
protected Task<T> createTask() {
return taskFactory.get();
}
}
public interface VoidCallable {
void call() throws Exception;
}
}

View File

@@ -75,28 +75,25 @@
<!-- Row 3.2 -->
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
<!-- Row 3.3 -->
<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="mountAfterUnlock" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.4 -->
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
<!-- Row 3.5 -->
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
<!-- Row 3.6 Alt1 -->
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.5 Alt1 -->
<Label GridPane.rowIndex="5" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.6 Alt2 -->
<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="useOwnMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
<!-- Row 3.5 Alt2 -->
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" fx:id="useOwnMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="7" GridPane.columnIndex="0" fx:id="mountPathLabel" text="%unlock.label.mountPath" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="mountPathLabel" text="%unlock.label.mountPath" cacheShape="true" cache="true" />
<HBox GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="mountPathBox" spacing="6.0">
<TextField GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="mountPath" cacheShape="true" cache="true" />
<HBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="mountPathBox" spacing="6.0">
<TextField fx:id="mountPath" cacheShape="true" cache="true" />
<Button text="%unlock.label.mountPathButton" fx:id="changeMountPathButton" onAction="#didClickchangeMountPathButton"/>
</HBox>

View File

@@ -28,18 +28,9 @@
<fx:define>
<ContextMenu fx:id="moreOptionsMenu">
<items>
<MenuItem fx:id="mountVaultMenuItem" text="%unlocked.moreOptions.mount" onAction="#didClickMountVault">
<graphic><Label text="&#xf139;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem fx:id="unmountVaultMenuItem" text="%unlocked.moreOptions.unmount" onAction="#didClickUnmountVault">
<graphic><Label text="&#xf131;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl">
<graphic><Label text="&#xf376;" styleClass="ionicons"/></graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>

View File

@@ -23,6 +23,9 @@ main.createVault.nonEmptyDir.content=The selected directory already contains fil
# welcome.fxml
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
welcome.newVersionMessage=Version %1$s can be downloaded.\nThis is %2$s.
welcome.askForUpdateCheck.dialog.title=Update check
welcome.askForUpdateCheck.dialog.header=Enable the integrated update check?
welcome.askForUpdateCheck.dialog.content=To check for updates, Cryptomator will fetch the current version from the Cryptomator servers and display a hint to you if a newer version is available.\n\nWe recommend to enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed. If you do not enable the update check you may check and download the current version from https://cryptomator.org/downloads/.\n\nYou can change this at any time from within the settings.
# initialize.fxml
initialize.label.password=Password
@@ -62,7 +65,6 @@ upgrade.version5toX.msg=This vault needs to be migrated to a newer format.\nPlea
# unlock.fxml
unlock.label.password=Password
unlock.label.savePassword=Save Password
unlock.label.mountAfterUnlock=Mount Drive
unlock.label.mountName=Drive Name
unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
unlock.label.revealAfterMount=Reveal Drive
@@ -100,10 +102,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed
# unlocked.fxml
unlocked.button.lock=Lock Vault
unlocked.moreOptions.mount=Mount Drive
unlocked.moreOptions.unmount=Eject Drive
unlocked.moreOptions.reveal=Reveal Drive
unlocked.moreOptions.copyUrl=Copy Filesystem URL
unlocked.label.mountFailed=Connecting drive failed
unlocked.label.revealFailed=Command failed
unlocked.label.unmountFailed=Ejecting drive failed
@@ -123,9 +122,10 @@ settings.webdav.port.apply=Apply
settings.webdav.prefGvfsScheme.label=WebDAV Scheme
settings.debugMode.label=Debug Mode *
settings.requiresRestartLabel=* Cryptomator needs to restart
settings.volume.label= Mount-Methode *
settings.volume.label=Preferred Volume Type
settings.volume.webdav=WebDAV
settings.volume.fuse=FUSE
settings.volume.dokany=Dokany
# tray icon
tray.menu.open=Open

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

View File

@@ -7,15 +7,14 @@ import org.mockito.Mockito;
public class PasswordStrengthUtilTest {
@Test
public void testLongPasswordsWillBeRatedAsStrong() {
@Test(timeout = 5000)
public void testLongPasswords() {
PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(Localization.class));
StringBuilder longPwBuilder = new StringBuilder();
for (int i = 0; i < 101; i++) {
for (int i = 0; i < 10000; i++) {
longPwBuilder.append('x');
}
int strength = util.computeRate(longPwBuilder.toString());
Assert.assertEquals(4, strength);
util.computeRate(longPwBuilder.toString());
}
}