Compare 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 StenzelandGitHub 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 StenzelandGitHub 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 StenzelandGitHub 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 StenzelandGitHub f1bf157cac Update stale.yml
[ci skip]
2018-06-17 16:20:00 +02:00
Sebastian StenzelandGitHub 9c75dd48dd Create no-response.yml
[ci skip]
2018-06-17 15:55:09 +02:00
Sebastian StenzelandGitHub 74d4b4ea47 Rename style.yml to stale.yml
[ci skip]
2018-06-17 15:49:19 +02:00
Sebastian StenzelandGitHub 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
+2 -2
View File
@@ -4,7 +4,7 @@
- Ensure you're running the latest version of Cryptomator. - 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 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). - 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? ## Did you write a patch that fixes a bug?
@@ -18,7 +18,7 @@
## Code of Conduct ## 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 ## Above all, thank you for your contributions
@@ -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 existing (and closed) issues: https://github.com/cryptomator/cryptomator/issues
- searched the knowledge base: https://community.cryptomator.org/c/kb - 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 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 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/CODE_OF_CONDUCT.md - have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
## Basic Info ## Basic Info
@@ -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
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
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
+17 -9
View File
@@ -1,7 +1,7 @@
language: java language: java
sudo: false sudo: false
jdk: jdk:
- oraclejdk9 - oraclejdk10
branches: branches:
except: except:
- continuous # To avoid infinite loops, as this tag is created by this Travis config - 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: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY
- secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN - secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN
- secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY - 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: addons:
apt: apt:
packages: packages:
@@ -28,13 +29,8 @@ install:
- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml - curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml
- mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 - mvn -fmain/pom.xml clean install -DskipTests 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: script:
- mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage - mvn --update-snapshots -fmain/pom.xml clean test 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
before_deploy: before_deploy:
- jdk_switcher use oraclejdk9
- | - |
if [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then 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'` 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 git remote remove gh
fi fi
- mvn -fmain/pom.xml clean package -Prelease -DskipTests - 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: deploy:
- provider: releases # CONTINUOUS - provider: releases # CONTINUOUS
prerelease: true prerelease: true
@@ -63,7 +61,8 @@ deploy:
file_glob: true file_glob: true
file: file:
- "main/uber-jar/target/Cryptomator-*.jar" - "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 skip_cleanup: true
name: Cryptomator continuous build name: Cryptomator continuous build
body: Automatically built on $(date +'%F %T %Z'). body: Automatically built on $(date +'%F %T %Z').
@@ -76,7 +75,8 @@ deploy:
api_key: $GITHUB_API_KEY api_key: $GITHUB_API_KEY
file: file:
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar" - "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 skip_cleanup: true
on: on:
repo: cryptomator/cryptomator repo: cryptomator/cryptomator
@@ -91,3 +91,11 @@ deploy:
on: on:
repo: cryptomator/cryptomator repo: cryptomator/cryptomator
tags: true 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
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-----
+1 -2
View File
@@ -46,8 +46,7 @@ For more information on the security details visit [cryptomator.org](https://cry
### Dependencies ### Dependencies
* Java 8 (min. 8u51, we recommend to use the current version) * Java 10 (min. 10.0.1, 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)
* Maven 3 * 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)) * 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))
+1 -1
View File
@@ -4,7 +4,7 @@
<id>tarball</id> <id>tarball</id>
<includeBaseDirectory>false</includeBaseDirectory> <includeBaseDirectory>false</includeBaseDirectory>
<formats> <formats>
<format>tar.gz</format> <format>zip</format>
</formats> </formats>
<fileSets> <fileSets>
<fileSet> <fileSet>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>ant-kit</artifactId> <artifactId>ant-kit</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
@@ -73,7 +73,7 @@
</executions> </executions>
</plugin> </plugin>
<!-- create antkit.tar.gz: --> <!-- create antkit.zip: -->
<plugin> <plugin>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0</version>
+13 -9
View File
@@ -4,9 +4,14 @@
<!-- Define application to build --> <!-- Define application to build -->
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" /> <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 --> <!-- Create main application jar -->
<target name="create-jar"> <target name="create-jar" depends="check-env">
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar"> <fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
<fx:application refid="Cryptomator" /> <fx:application refid="Cryptomator" />
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" /> <fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
@@ -21,27 +26,26 @@
</fx:jar> </fx:jar>
</target> </target>
<!-- Create Image package --> <!-- Create Image -->
<target name="image" depends="create-jar"> <target name="image" depends="create-jar">
<fx:deploy nativeBundles="image" outdir="antbuild" verbose="true"> <fx:deploy nativeBundles="image" outdir="antbuild" verbose="true">
<fx:application refid="Cryptomator" /> <fx:application refid="Cryptomator" />
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="GPL" category="Utility"/> <fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="GPL" category="Utility"/>
<fx:platform j2se="9.0"> <fx:platform j2se="10">
<fx:property name="logback.configurationFile" value="logback.xml" /> <fx:property name="logback.configurationFile" value="\${antbuild.logback.configurationFile}" />
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" /> <fx:property name="cryptomator.settingsPath" value="\${antbuild.cryptomator.settingsPath}" />
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" /> <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="-Xss2m"/>
<fx:jvmarg value="-Xmx512m"/> <fx:jvmarg value="-Xmx512m"/>
<fx:jvmarg value="--add-modules"/>
<fx:jvmarg value="jdk.incubator.httpclient"/>
</fx:platform> </fx:platform>
<fx:resources> <fx:resources>
<fx:fileset dir="." type="data" includes="logback.xml" />
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" /> <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:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
</fx:resources> </fx:resources>
<fx:permissions elevated="false" /> <fx:permissions elevated="false" />
<fx:preferences install="true" /> <fx:preferences install="true" />
<fx:bundleArgument arg="dropinResourcesRoot" value="\${antbuild.dropinResourcesRoot}"/>
</fx:deploy> </fx:deploy>
</target> </target>
@@ -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>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>commons</artifactId> <artifactId>commons</artifactId>
<name>Cryptomator Commons</name> <name>Cryptomator Commons</name>
@@ -8,39 +8,34 @@
******************************************************************************/ ******************************************************************************/
package org.cryptomator.common.settings; package org.cryptomator.common.settings;
import java.util.function.Consumer; import javafx.beans.property.*;
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.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import java.util.function.Consumer;
public class Settings { public class Settings {
public static final int MIN_PORT = 1024; public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535; 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_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final String DEFAULT_GVFS_SCHEME = "dav"; public static final String DEFAULT_GVFS_SCHEME = "dav";
public static final boolean DEFAULT_DEBUG_MODE = false; 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 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 BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT); private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS); private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME); private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); 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; private Consumer<Settings> saveCmd;
@@ -49,12 +44,13 @@ public class Settings {
*/ */
Settings() { Settings() {
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save()); directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
askedForUpdateCheck.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged); checkForUpdates.addListener(this::somethingChanged);
port.addListener(this::somethingChanged); port.addListener(this::somethingChanged);
numTrayNotifications.addListener(this::somethingChanged); numTrayNotifications.addListener(this::somethingChanged);
preferredGvfsScheme.addListener(this::somethingChanged); preferredGvfsScheme.addListener(this::somethingChanged);
debugMode.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged);
volumeImpl.addListener(this::somethingChanged); preferredVolumeImpl.addListener(this::somethingChanged);
} }
void setSaveCmd(Consumer<Settings> saveCmd) { void setSaveCmd(Consumer<Settings> saveCmd) {
@@ -77,6 +73,10 @@ public class Settings {
return directories; return directories;
} }
public BooleanProperty askedForUpdateCheck() {
return askedForUpdateCheck;
}
public BooleanProperty checkForUpdates() { public BooleanProperty checkForUpdates() {
return checkForUpdates; return checkForUpdates;
} }
@@ -97,8 +97,8 @@ public class Settings {
return debugMode; return debugMode;
} }
public ObjectProperty<VolumeImpl> volumeImpl() { public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
return volumeImpl; return preferredVolumeImpl;
} }
} }
@@ -5,17 +5,16 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.common.settings; 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.TypeAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import 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> { public class SettingsJsonAdapter extends TypeAdapter<Settings> {
@@ -28,12 +27,13 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
out.beginObject(); out.beginObject();
out.name("directories"); out.name("directories");
writeVaultSettingsArray(out, value.getDirectories()); writeVaultSettingsArray(out, value.getDirectories());
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get()); out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("port").value(value.port().get()); out.name("port").value(value.port().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get()); out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get()); out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
out.name("debugMode").value(value.debugMode().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(); out.endObject();
} }
@@ -56,6 +56,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "directories": case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in)); settings.getDirectories().addAll(readVaultSettingsArray(in));
break; break;
case "askedForUpdateCheck":
settings.askedForUpdateCheck().set(in.nextBoolean());
break;
case "checkForUpdatesEnabled": case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean()); settings.checkForUpdates().set(in.nextBoolean());
break; break;
@@ -71,8 +74,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
case "debugMode": case "debugMode":
settings.debugMode().set(in.nextBoolean()); settings.debugMode().set(in.nextBoolean());
break; break;
case "volumeImpl": case "preferredVolumeImpl":
settings.volumeImpl().set(parseNioAdapterName(in.nextString())); settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
break; break;
default: default:
LOG.warn("Unsupported vault setting found in JSON: " + name); LOG.warn("Unsupported vault setting found in JSON: " + name);
@@ -84,11 +87,11 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
return settings; return settings;
} }
private VolumeImpl parseNioAdapterName(String nioAdapterName) { private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
try { try {
return VolumeImpl.valueOf(nioAdapterName); return VolumeImpl.valueOf(nioAdapterName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return Settings.DEFAULT_VOLUME_IMPL; return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
} }
} }
@@ -26,7 +26,6 @@ import javafx.beans.property.StringProperty;
public class VaultSettings { public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false; 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_REAVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false; 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 mountName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty(); private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); 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 revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
private final StringProperty individualMountPath = new SimpleStringProperty(); private final StringProperty individualMountPath = new SimpleStringProperty();
@@ -47,7 +45,7 @@ public class VaultSettings {
} }
Observable[] observables() { 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) { private void deriveMountNameFromPath(Path path) {
@@ -118,10 +116,6 @@ public class VaultSettings {
return unlockAfterStartup; return unlockAfterStartup;
} }
public BooleanProperty mountAfterUnlock() {
return mountAfterUnlock;
}
public BooleanProperty revealAfterMount() { public BooleanProperty revealAfterMount() {
return revealAfterMount; return revealAfterMount;
} }
@@ -25,7 +25,6 @@ class VaultSettingsJsonAdapter {
out.name("mountName").value(value.mountName().get()); out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get()); out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get()); out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("mountAfterUnlock").value(value.mountAfterUnlock().get());
out.name("revealAfterMount").value(value.revealAfterMount().get()); out.name("revealAfterMount").value(value.revealAfterMount().get());
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().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!) //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 individualMountPath = null;
String winDriveLetter = null; String winDriveLetter = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP; boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
@@ -63,9 +61,6 @@ class VaultSettingsJsonAdapter {
case "unlockAfterStartup": case "unlockAfterStartup":
unlockAfterStartup = in.nextBoolean(); unlockAfterStartup = in.nextBoolean();
break; break;
case "mountAfterUnlock":
mountAfterUnlock = in.nextBoolean();
break;
case "revealAfterMount": case "revealAfterMount":
revealAfterMount = in.nextBoolean(); revealAfterMount = in.nextBoolean();
break; break;
@@ -87,7 +82,6 @@ class VaultSettingsJsonAdapter {
vaultSettings.path().set(Paths.get(path)); vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter); vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup); vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
vaultSettings.revealAfterMount().set(revealAfterMount); vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath); vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
vaultSettings.individualMountPath().set(individualMountPath); vaultSettings.individualMountPath().set(individualMountPath);
@@ -4,7 +4,8 @@ import java.util.Arrays;
public enum VolumeImpl { public enum VolumeImpl {
WEBDAV("WebDAV"), WEBDAV("WebDAV"),
FUSE("FUSE"); FUSE("FUSE"),
DOKANY("DOKANY");
private String displayName; private String displayName;
@@ -22,7 +22,7 @@ public class SettingsJsonAdapterTest {
+ "\"checkForUpdatesEnabled\": true,"// + "\"checkForUpdatesEnabled\": true,"//
+ "\"port\": 8080,"// + "\"port\": 8080,"//
+ "\"numTrayNotifications\": 42,"// + "\"numTrayNotifications\": 42,"//
+ "\"volumeImpl\": \"FUSE\"}"; + "\"preferredVolumeImpl\": \"FUSE\"}";
Settings settings = adapter.fromJson(json); Settings settings = adapter.fromJson(json);
@@ -31,7 +31,7 @@ public class SettingsJsonAdapterTest {
Assert.assertEquals(8080, settings.port().get()); Assert.assertEquals(8080, settings.port().get());
Assert.assertEquals(42, settings.numTrayNotifications().get()); Assert.assertEquals(42, settings.numTrayNotifications().get());
Assert.assertEquals("dav", settings.preferredGvfsScheme().get()); Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
Assert.assertEquals(VolumeImpl.FUSE, settings.volumeImpl().get()); Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
} }
} }
-1
View File
@@ -1 +0,0 @@
/target/
-50
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>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>keychain</artifactId> <artifactId>keychain</artifactId>
<name>System Keychain Access</name> <name>System Keychain Access</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>launcher</artifactId> <artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name> <name>Cryptomator Launcher</name>
@@ -37,6 +37,8 @@ public class Cryptomator {
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", 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. System.exit(0); // end remaining non-daemon threads.
} }
+32 -19
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Cryptomator</name> <name>Cryptomator</name>
@@ -25,26 +25,27 @@
<!-- dependency versions --> <!-- dependency versions -->
<cryptomator.cryptolib.version>1.2.0</cryptomator.cryptolib.version> <cryptomator.cryptolib.version>1.2.0</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.5.1</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>1.5.2</cryptomator.cryptofs.version>
<cryptomator.webdav.version>1.0.4</cryptomator.webdav.version>
<cryptomator.jni.version>2.0.0</cryptomator.jni.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-io.version>2.5</commons-io.version>
<commons-lang3.version>3.6</commons-lang3.version> <commons-lang3.version>3.6</commons-lang3.version>
<easybind.version>1.0.3</easybind.version> <easybind.version>1.0.3</easybind.version>
<guava.version>23.6-jre</guava.version> <guava.version>25.1-jre</guava.version>
<dagger.version>2.14.1</dagger.version> <dagger.version>2.16</dagger.version>
<gson.version>2.8.2</gson.version> <gson.version>2.8.5</gson.version>
<slf4j.version>1.7.25</slf4j.version> <slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
<junit.version>4.12</junit.version> <junit.version>4.12</junit.version>
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.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 --> <hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
</properties> </properties>
@@ -59,6 +60,10 @@
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories> </repositories>
<dependencyManagement> <dependencyManagement>
@@ -101,6 +106,11 @@
<artifactId>fuse-nio-adapter</artifactId> <artifactId>fuse-nio-adapter</artifactId>
<version>${cryptomator.fuse.version}</version> <version>${cryptomator.fuse.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>dokany-nio-adapter</artifactId>
<version>${cryptomator.dokany.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter</artifactId> <artifactId>webdav-nio-adapter</artifactId>
@@ -245,9 +255,6 @@
</profile> </profile>
<profile> <profile>
<id>coverage</id> <id>coverage</id>
<modules>
<module>jacoco-report</module>
</modules>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@@ -264,7 +271,7 @@
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>3.0.2</version> <version>3.1.0</version>
<executions> <executions>
<execution> <execution>
<id>copy-libs</id> <id>copy-libs</id>
@@ -281,7 +288,7 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version> <version>0.8.1</version>
<executions> <executions>
<execution> <execution>
<id>prepare-agent</id> <id>prepare-agent</id>
@@ -289,6 +296,12 @@
<goal>prepare-agent</goal> <goal>prepare-agent</goal>
</goals> </goals>
</execution> </execution>
<execution>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions> </executions>
<configuration> <configuration>
<excludes> <excludes>
@@ -304,9 +317,9 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version> <version>3.7.0</version>
<configuration> <configuration>
<source>9</source> <source>10</source>
<target>9</target> <target>10</target>
<release>9</release> <release>10</release>
<compilerArgs> <compilerArgs>
<arg>--add-modules</arg> <arg>--add-modules</arg>
<arg>jdk.incubator.httpclient</arg> <arg>jdk.incubator.httpclient</arg>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>uber-jar</artifactId> <artifactId>uber-jar</artifactId>
<name>Single über jar with all dependencies</name> <name>Single über jar with all dependencies</name>
+12 -7
View File
@@ -4,35 +4,40 @@
<parent> <parent>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>main</artifactId> <artifactId>main</artifactId>
<version>1.4.0-SNAPSHOT</version> <version>1.4.0-beta2</version>
</parent> </parent>
<artifactId>ui</artifactId> <artifactId>ui</artifactId>
<name>Cryptomator GUI</name> <name>Cryptomator GUI</name>
<dependencies> <dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId> <artifactId>commons</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId> <artifactId>cryptofs</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>webdav-nio-adapter</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId> <artifactId>jni</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>keychain</artifactId> <artifactId>fuse-nio-adapter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <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> </dependency>
<!-- CryptoLib --> <!-- CryptoLib -->
@@ -53,6 +53,7 @@ public class ExitUtil {
private final Localization localization; private final Localization localization;
private final Settings settings; private final Settings settings;
private final Optional<MacFunctions> macFunctions; private final Optional<MacFunctions> macFunctions;
private TrayIcon trayIcon;
@Inject @Inject
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) { 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) { private void initTrayIconExitHandler(Runnable exitCommand) {
final TrayIcon trayIcon = createTrayIcon(exitCommand); trayIcon = createTrayIcon(exitCommand);
try { try {
// double clicking tray icon should open Cryptomator // double clicking tray icon should open Cryptomator
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
@@ -118,14 +119,7 @@ public class ExitUtil {
exitItem.addActionListener(e -> exitCommand.run()); exitItem.addActionListener(e -> exitCommand.run());
popup.add(exitItem); popup.add(exitItem);
final Image image; final Image image = getAppropriateTrayIconImage(true);
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"));
}
return new TrayIcon(image, localization.getString("app.name"), popup); 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));
}
} }
@@ -11,6 +11,8 @@ package org.cryptomator.ui;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.inject.Named; import javax.inject.Named;
@@ -32,16 +34,38 @@ import org.fxmisc.easybind.EasyBind;
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class}) @Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
public class UiModule { public class UiModule {
private static final int NUM_SCHEDULER_THREADS = 4;
@Provides @Provides
@Singleton @Singleton
Settings provideSettings(SettingsProvider settingsProvider) { Settings provideSettings(SettingsProvider settingsProvider) {
return settingsProvider.get(); 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 @Provides
@Singleton @Singleton
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) { 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); shutdownTaskScheduler.accept(executorService::shutdown);
return executorService; return executorService;
} }
@@ -125,6 +125,7 @@ public class MainController implements ViewController {
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy); this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED)); this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon);
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit); EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
autoUnlocker.unlockAllSilently(); autoUnlocker.unlockAllSilently();
@@ -395,7 +396,7 @@ public class MainController implements ViewController {
return; return;
} }
if (newValue.getState() != Vault.State.LOCKED) { if (newValue.getState() != Vault.State.LOCKED) {
this.showUnlockedView(newValue); this.showUnlockedView(newValue, false);
} else if (!newValue.doesVaultDirectoryExist()) { } else if (!newValue.doesVaultDirectoryExist()) {
this.showNotFoundView(); this.showNotFoundView();
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) { } else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
@@ -494,22 +495,25 @@ public class MainController implements ViewController {
public void didUnlock(Vault vault) { public void didUnlock(Vault vault) {
if (vault.equals(selectedVault.getValue())) { if (vault.equals(selectedVault.getValue())) {
this.showUnlockedView(vault); this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue());
} }
} }
private void showUnlockedView(Vault vault) { private void showUnlockedView(Vault vault, boolean reveal) {
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> { final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml"));
return viewControllerLoader.load("/fxml/unlocked.fxml");
});
ctrl.setVault(vault); ctrl.setVault(vault);
ctrl.setListener(this::didLock); ctrl.setListener(this::didLock);
if (reveal) {
ctrl.revealVault(vault);
}
activeController.set(ctrl); activeController.set(ctrl);
} }
public void didLock(UnlockedController ctrl) { public void didLock(UnlockedController ctrl) {
unlockedVaults.remove(ctrl.getVault()); unlockedVaults.remove(ctrl.getVault());
showUnlockView(UnlockController.State.UNLOCKING); if (ctrl.getVault().getId() == selectedVault.get().getId()) {
showUnlockView(UnlockController.State.UNLOCKING);
}
activeController.get().focus(); activeController.get().focus();
} }
@@ -77,9 +77,6 @@ public class SettingsController implements ViewController {
@FXML @FXML
private ChoiceBox<String> prefGvfsScheme; private ChoiceBox<String> prefGvfsScheme;
@FXML
private Label volumeLabel;
@FXML @FXML
private ChoiceBox<VolumeImpl> volume; private ChoiceBox<VolumeImpl> volume;
@@ -97,8 +94,7 @@ public class SettingsController implements ViewController {
//NIOADAPTER //NIOADAPTER
volume.getItems().addAll(getSupportedAdapters()); volume.getItems().addAll(getSupportedAdapters());
volume.setValue(settings.volumeImpl().get()); volume.setValue(settings.preferredVolumeImpl().get());
volume.setVisible(true);
volume.setConverter(new NioAdapterImplStringConverter()); volume.setConverter(new NioAdapterImplStringConverter());
//WEBDAV //WEBDAV
@@ -127,13 +123,13 @@ public class SettingsController implements ViewController {
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty()); settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty()); settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
settings.volumeImpl().bind(volume.valueProperty()); settings.preferredVolumeImpl().bind(volume.valueProperty());
settings.debugMode().bind(debugModeCheckbox.selectedProperty()); settings.debugMode().bind(debugModeCheckbox.selectedProperty());
} }
//TODO: how to implement this?
private VolumeImpl[] getSupportedAdapters() { private VolumeImpl[] getSupportedAdapters() {
return new VolumeImpl[]{VolumeImpl.FUSE, VolumeImpl.WEBDAV}; // TODO: filter depending on supported drivers
return VolumeImpl.values();
} }
@Override @Override
@@ -8,41 +8,21 @@
******************************************************************************/ ******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import java.io.IOException; import javax.inject.Inject;
import java.nio.file.*; 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.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService;
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 com.google.common.base.CharMatcher; import com.google.common.base.CharMatcher;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import javafx.application.Application; import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -59,8 +39,28 @@ import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.util.StringConverter; 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 { public class UnlockController implements ViewController {
@@ -73,23 +73,23 @@ public class UnlockController implements ViewController {
private final Application app; private final Application app;
private final Localization localization; private final Localization localization;
private final AsyncTaskService asyncTaskService;
private final WindowsDriveLetters driveLetters; private final WindowsDriveLetters driveLetters;
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange; private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
private final Optional<KeychainAccess> keychainAccess; private final Optional<KeychainAccess> keychainAccess;
private final Settings settings; private final Settings settings;
private final ExecutorService executor;
private Vault vault; private Vault vault;
private Optional<UnlockListener> listener = Optional.empty(); private Optional<UnlockListener> listener = Optional.empty();
private Subscription vaultSubs = Subscription.EMPTY; private Subscription vaultSubs = Subscription.EMPTY;
@Inject @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.app = app;
this.localization = localization; this.localization = localization;
this.asyncTaskService = asyncTaskService;
this.driveLetters = driveLetters; this.driveLetters = driveLetters;
this.keychainAccess = keychainAccess; this.keychainAccess = keychainAccess;
this.settings = settings; this.settings = settings;
this.executor = executor;
} }
@FXML @FXML
@@ -107,9 +107,6 @@ public class UnlockController implements ViewController {
@FXML @FXML
private CheckBox savePassword; private CheckBox savePassword;
@FXML
private CheckBox mountAfterUnlock;
@FXML @FXML
private TextField mountName; private TextField mountName;
@@ -159,8 +156,6 @@ public class UnlockController implements ViewController {
public void initialize() { public void initialize() {
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty()); advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty()); 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.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange); mountName.textProperty().addListener(this::mountNameDidChange);
savePassword.setDisable(!keychainAccess.isPresent()); savePassword.setDisable(!keychainAccess.isPresent());
@@ -186,7 +181,7 @@ public class UnlockController implements ViewController {
winDriveLetterLabel.setManaged(false); winDriveLetterLabel.setManaged(false);
winDriveLetter.setVisible(false); winDriveLetter.setVisible(false);
winDriveLetter.setManaged(false); winDriveLetter.setManaged(false);
if (VolumeImpl.WEBDAV.equals(settings.volumeImpl().get())) { if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
useOwnMountPath.setVisible(false); useOwnMountPath.setVisible(false);
useOwnMountPath.setManaged(false); useOwnMountPath.setManaged(false);
mountPathLabel.setManaged(false); mountPathLabel.setManaged(false);
@@ -250,12 +245,10 @@ public class UnlockController implements ViewController {
} }
VaultSettings vaultSettings = vault.getVaultSettings(); VaultSettings vaultSettings = vault.getVaultSettings();
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get()); unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
mountAfterUnlock.setSelected(vaultSettings.mountAfterUnlock().get());
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get()); revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
useOwnMountPath.setSelected(vaultSettings.usesIndividualMountPath().get()); useOwnMountPath.setSelected(vaultSettings.usesIndividualMountPath().get());
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set)); 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(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set)); vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
@@ -430,7 +423,7 @@ public class UnlockController implements ViewController {
progressIndicator.setVisible(true); progressIndicator.setVisible(true);
CharSequence password = passwordField.getCharacters(); CharSequence password = passwordField.getCharacters();
asyncTaskService.asyncTaskOf(() -> { Tasks.create(() -> {
vault.unlock(password); vault.unlock(password);
if (keychainAccess.isPresent() && savePassword.isSelected()) { if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password); keychainAccess.get().storePassphrase(vault.getId(), password);
@@ -457,16 +450,16 @@ public class UnlockController implements ViewController {
}).onError(ServerLifecycleException.class, e -> { }).onError(ServerLifecycleException.class, e -> {
LOG.error("Unlock failed for technical reasons.", e); LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed")); messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
}).onError(IOException.class, e -> { }).onError(Exception.class, e -> {
LOG.error("Unlock failed for technical reasons.", e); LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed")); messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
}).andFinally(() -> { }).andFinally(() -> {
if (!savePassword.isSelected()) { if (!savePassword.isSelected()) {
passwordField.swipe(); passwordField.swipe();
} }
advancedOptions.setDisable(false); advancedOptions.setDisable(false);
progressIndicator.setVisible(false); progressIndicator.setVisible(false);
}).run(); }).runOnce(executor);
} }
/* callback */ /* callback */
@@ -8,29 +8,13 @@
******************************************************************************/ ******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import static java.lang.String.format;
import java.io.IOException;
import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Optional;
import org.cryptomator.frontend.webdav.ServerLifecycleException; import java.util.concurrent.ExecutorService;
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 javafx.animation.Animation; import javafx.animation.Animation;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -46,13 +30,19 @@ import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.PopupWindow.AnchorLocation; import javafx.stage.PopupWindow.AnchorLocation;
import javafx.util.Duration; 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 { public class UnlockedController implements ViewController {
@@ -62,9 +52,8 @@ public class UnlockedController implements ViewController {
private static final double IO_SAMPLING_INTERVAL = 0.25; private static final double IO_SAMPLING_INTERVAL = 0.25;
private final Localization localization; private final Localization localization;
private final AsyncTaskService asyncTaskService; private final ExecutorService executor;
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>(); 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 Optional<LockListener> listener = Optional.empty();
private Timeline ioAnimation; private Timeline ioAnimation;
@@ -83,30 +72,17 @@ public class UnlockedController implements ViewController {
@FXML @FXML
private ContextMenu moreOptionsMenu; private ContextMenu moreOptionsMenu;
@FXML
private MenuItem mountVaultMenuItem;
@FXML
private MenuItem unmountVaultMenuItem;
@FXML
private MenuItem revealVaultMenuItem;
@FXML @FXML
private VBox root; private VBox root;
@Inject @Inject
public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) { public UnlockedController(Localization localization, ExecutorService executor) {
this.localization = localization; this.localization = localization;
this.asyncTaskService = asyncTaskService; this.executor = executor;
} }
@Override @Override
public void initialize() { 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(vault, this::vaultChanged);
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected); EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
} }
@@ -121,10 +97,6 @@ public class UnlockedController implements ViewController {
return; return;
} }
if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) {
mountVault(newVault);
}
// (re)start throughput statistics: // (re)start throughput statistics:
stopIoSampling(); stopIoSampling();
startIoSampling(); startIoSampling();
@@ -132,75 +104,22 @@ public class UnlockedController implements ViewController {
@FXML @FXML
private void didClickLockVault(ActionEvent event) { private void didClickLockVault(ActionEvent event) {
regularUnmountVault(this::lockVault); regularLockVault(this::lockVaultSucceeded);
} }
private void lockVault() { private void lockVaultSucceeded() {
try {
vault.get().lock();
} catch (ServerLifecycleException | IOException e) {
LOG.error("Lock failed", e);
}
listener.ifPresent(listener -> listener.didLock(this)); listener.ifPresent(listener -> listener.didLock(this));
} }
@FXML private void regularLockVault(Runnable onSuccess) {
private void didClickMoreOptions(ActionEvent event) { Tasks.create(() -> {
if (moreOptionsMenu.isShowing()) { vault.get().lock(false);
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();
}).onSuccess(() -> { }).onSuccess(() -> {
LOG.trace("Regular unmount succeeded."); LOG.trace("Regular unmount succeeded.");
onSuccess.run(); onSuccess.run();
}).onError(Exception.class, e -> { }).onError(Exception.class, e -> {
onRegularUnmountVaultFailed(e, onSuccess); onRegularUnmountVaultFailed(e, onSuccess);
}).run(); }).runOnce(executor);
}
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();
} }
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) { private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
@@ -214,7 +133,7 @@ public class UnlockedController implements ViewController {
Optional<ButtonType> choice = confirmDialog.showAndWait(); Optional<ButtonType> choice = confirmDialog.showAndWait();
if (ButtonType.YES.equals(choice.get())) { if (ButtonType.YES.equals(choice.get())) {
forcedUnmountVault(onSuccess); forcedLockVault(onSuccess);
} else { } else {
LOG.trace("Unmount cancelled.", e); 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 @FXML
private void didClickRevealVault(ActionEvent event) { private void didClickRevealVault(ActionEvent event) {
revealVault(vault.get()); revealVault(vault.get());
} }
private void revealVault(Vault vault) { void revealVault(Vault vault) {
asyncTaskService.asyncTaskOf(() -> { Tasks.create(() -> {
vault.reveal(); vault.reveal();
}).onSuccess(() -> { }).onSuccess(() -> {
LOG.trace("Reveal succeeded."); LOG.trace("Reveal succeeded.");
messageLabel.setText(null); messageLabel.setText(null);
}).onError(CommandFailedException.class, e -> { }).onError(Exception.class, e -> {
LOG.error("Reveal failed.", e); LOG.error("Reveal failed.", e);
messageLabel.setText(localization.getString("unlocked.label.revealFailed")); messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
}).run(); }).runOnce(executor);
}
@FXML
private void didClickCopyUrl(ActionEvent event) {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putUrl(vault.get().getFilesystemRootUrl());
clipboardContent.putString(vault.get().getFilesystemRootUrl());
Clipboard.getSystemClipboard().setContent(clipboardContent);
} }
// **************************************** // ****************************************
@@ -5,19 +5,10 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService;
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 javafx.beans.binding.BooleanExpression; import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
@@ -30,19 +21,26 @@ import javafx.scene.control.CheckBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane; 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 { public class UpgradeController implements ViewController {
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>(); private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
private final UpgradeStrategies strategies; private final UpgradeStrategies strategies;
private final AsyncTaskService asyncTaskService; private final ExecutorService executor;
private Optional<UpgradeListener> listener = Optional.empty(); private Optional<UpgradeListener> listener = Optional.empty();
private Vault vault; private Vault vault;
@Inject @Inject
public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) { public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
this.strategies = strategies; this.strategies = strategies;
this.asyncTaskService = asyncTaskService; this.executor = executor;
} }
@FXML @FXML
@@ -122,8 +120,8 @@ public class UpgradeController implements ViewController {
private void upgrade(UpgradeStrategy instruction) { private void upgrade(UpgradeStrategy instruction) {
passwordField.setDisable(true); passwordField.setDisable(true);
progressIndicator.setVisible(true); progressIndicator.setVisible(true);
asyncTaskService // Tasks //
.asyncTaskOf(() -> { .create(() -> {
if (!instruction.isApplicable(vault)) { if (!instruction.isApplicable(vault)) {
throw new IllegalStateException("No ugprade needed for " + vault.getPath()); throw new IllegalStateException("No ugprade needed for " + vault.getPath());
} }
@@ -137,7 +135,7 @@ public class UpgradeController implements ViewController {
progressIndicator.setVisible(false); progressIndicator.setVisible(false);
passwordField.setDisable(false); passwordField.setDisable(false);
passwordField.swipe(); passwordField.swipe();
}).run(); }).runOnce(executor);
} }
private void showNextUpgrade() { private void showNextUpgrade() {
@@ -2,24 +2,27 @@
* Copyright (c) 2014, 2017 Sebastian Stenzel * Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved. * All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
* *
* Contributors: * Contributors:
* Sebastian Stenzel - initial API and implementation * Sebastian Stenzel - initial API and implementation
******************************************************************************/ ******************************************************************************/
package org.cryptomator.ui.controllers; 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.URI;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject; import java.util.concurrent.TimeUnit;
import javax.inject.Named;
import javax.inject.Singleton;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@@ -30,20 +33,20 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Hyperlink; import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox; 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.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.l10n.Localization; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
@Singleton @Singleton
public class WelcomeController implements ViewController { public class WelcomeController implements ViewController {
@@ -54,17 +57,17 @@ public class WelcomeController implements ViewController {
private final Localization localization; private final Localization localization;
private final Settings settings; private final Settings settings;
private final Comparator<String> semVerComparator; private final Comparator<String> semVerComparator;
private final AsyncTaskService asyncTaskService; private final ScheduledExecutorService executor;
@Inject @Inject
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator, 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.app = app;
this.applicationVersion = applicationVersion; this.applicationVersion = applicationVersion;
this.localization = localization; this.localization = localization;
this.settings = settings; this.settings = settings;
this.semVerComparator = semVerComparator; this.semVerComparator = semVerComparator;
this.asyncTaskService = asyncTaskService; this.executor = executor;
} }
@FXML @FXML
@@ -86,6 +89,8 @@ public class WelcomeController implements ViewController {
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
if (areUpdatesManagedExternally()) { if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false); checkForUpdatesContainer.setVisible(false);
} else if (!settings.askedForUpdateCheck().get()) {
this.askForUpdateCheck();
} else if (settings.checkForUpdates().get()) { } else if (settings.checkForUpdates().get()) {
this.checkForUpdates(); this.checkForUpdates();
} }
@@ -104,34 +109,63 @@ public class WelcomeController implements ViewController {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false")); 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() { private void checkForUpdates() {
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking")); checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
checkForUpdatesIndicator.setVisible(true); 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); 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(); URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL();
HttpRequest request = HttpRequest.newBuilder() HttpURLConnection conn = (HttpURLConnection) url.openConnection();
.GET() conn.addRequestProperty("User-Agent", userAgent);
.uri(new URI("https://api.cryptomator.org/updates/latestVersion.json")) conn.connect();
.header("User-Agent", userAgent) try {
.timeout(Duration.ofSeconds(5)) if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
.build(); return Optional.<byte[]>empty();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8)); }
if (response.statusCode() == 200) { 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(); 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()); }.getType());
if (map != null) { if (map != null) {
this.compareVersions(map); this.compareVersions(map);
} }
} });
}).onError(Exception.class, e -> {
LOG.warn("Error checking for updates", e);
}).andFinally(() -> { }).andFinally(() -> {
checkForUpdatesStatus.setText(""); checkForUpdatesStatus.setText("");
checkForUpdatesIndicator.setVisible(false); checkForUpdatesIndicator.setVisible(false);
}).run(); }).runOnce(executor);
} }
private void compareVersions(final Map<String, String> latestVersions) { private void compareVersions(final Map<String, String> latestVersions) {
assert Platform.isFxApplicationThread();
final String latestVersion; final String latestVersion;
if (SystemUtils.IS_OS_MAC_OSX) { if (SystemUtils.IS_OS_MAC_OSX) {
latestVersion = latestVersions.get("mac"); latestVersion = latestVersions.get("mac");
@@ -147,11 +181,9 @@ public class WelcomeController implements ViewController {
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion); LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) { if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion); final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
Platform.runLater(() -> { this.updateLink.setText(msg);
this.updateLink.setText(msg); this.updateLink.setVisible(true);
this.updateLink.setVisible(true); this.updateLink.setDisable(false);
this.updateLink.setDisable(false);
});
} }
} }
@@ -68,9 +68,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
} }
switch (state) { switch (state) {
case UNLOCKED: case UNLOCKED:
case MOUNTED: case PROCESSING:
case MOUNTING:
case UNMOUNTING:
return "\uf09c"; return "\uf09c";
case LOCKED: case LOCKED:
default: default:
@@ -84,9 +82,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
} }
switch (state) { switch (state) {
case UNLOCKED: case UNLOCKED:
case MOUNTED: case PROCESSING:
case MOUNTING:
case UNMOUNTING:
return UNLOCKED_ICON_COLOR; return UNLOCKED_ICON_COLOR;
case LOCKED: case LOCKED:
default: default:
@@ -73,33 +73,21 @@ public class AutoUnlocker {
} }
try { try {
vault.unlock(CharBuffer.wrap(storedPw)); vault.unlock(CharBuffer.wrap(storedPw));
mountSilently(vault); revealSilently(vault);
} catch (IOException | CryptoException e) { } catch (IOException | CryptoException | Volume.VolumeException e) {
LOG.error("Auto unlock failed.", e); LOG.error("Auto unlock failed.", e);
} finally { } finally {
Arrays.fill(storedPw, ' '); 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) { private void revealSilently(Vault mountedVault) {
if (!mountedVault.getVaultSettings().revealAfterMount().get()) { if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
return; return;
} }
try { try {
mountedVault.reveal(); mountedVault.reveal();
} catch (CommandFailedException e) { } catch (Volume.VolumeException e) {
LOG.error("Auto unlock succeded, but revealing the drive failed.", e); LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
} }
} }
@@ -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);
}
}
@@ -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();
}
}
@@ -2,6 +2,7 @@ package org.cryptomator.ui.model;
import java.io.IOException; import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -11,6 +12,7 @@ import javax.inject.Inject;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem; 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.EnvironmentVariables;
import org.cryptomator.frontend.fuse.mount.FuseMountFactory; import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@VaultModule.PerVault
public class FuseVolume implements Volume { public class FuseVolume implements Volume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); 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 static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
private final VaultSettings vaultSettings; private final VaultSettings vaultSettings;
private final WindowsDriveLetters windowsDriveLetters;
private Mount fuseMnt; private Mount fuseMnt;
private CryptoFileSystem cfs;
private Path mountPath; private Path mountPath;
private boolean extraDirCreated; private boolean extraDirCreated;
@Inject @Inject
public FuseVolume(VaultSettings vaultSettings, WindowsDriveLetters windowsDriveLetters) { public FuseVolume(VaultSettings vaultSettings) {
this.vaultSettings = vaultSettings; this.vaultSettings = vaultSettings;
this.windowsDriveLetters = windowsDriveLetters;
this.extraDirCreated = false; this.extraDirCreated = false;
} }
@Override @Override
public void prepare(CryptoFileSystem fs) throws IOException, FuseNotSupportedException { public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
this.cfs = fs;
String mountPath; String mountPath;
if (SystemUtils.IS_OS_WINDOWS) { if (vaultSettings.usesIndividualMountPath().get()) {
//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()) {
//specific path given //specific path given
mountPath = vaultSettings.individualMountPath().get(); mountPath = vaultSettings.individualMountPath().get();
} else { } else {
@@ -66,57 +54,60 @@ public class FuseVolume implements Volume {
extraDirCreated = true; extraDirCreated = true;
} }
this.mountPath = Paths.get(mountPath).toAbsolutePath(); this.mountPath = Paths.get(mountPath).toAbsolutePath();
mount(fs.getPath("/"));
} }
private String createDirIfNotExist(String prefix, String dirName) throws IOException { private String createDirIfNotExist(String prefix, String dirName) throws IOException {
Path p = Paths.get(prefix, dirName + vaultSettings.getId()); Path p = Paths.get(prefix, dirName + vaultSettings.getId());
if (Files.isDirectory(p) && !Files.newDirectoryStream(p).iterator().hasNext()) { if (Files.isDirectory(p)) {
throw new DirectoryNotEmptyException("Mount point is not empty."); 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 { } else {
Files.createDirectory(p); Files.createDirectory(p);
return p.toString(); return p.toString();
} }
} }
@Override private void mount(Path root) throws VolumeException {
public void mount() throws CommandFailedException {
try { try {
EnvironmentVariables envVars = EnvironmentVariables.create() EnvironmentVariables envVars = EnvironmentVariables.create()
.withMountName(vaultSettings.mountName().getValue()) .withMountName(vaultSettings.mountName().getValue())
.withMountPath(mountPath) .withMountPath(mountPath)
.build(); .build();
this.fuseMnt = FuseMountFactory.getMounter().mount(cfs.getPath("/"), envVars); this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars);
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { } catch (CommandFailedException e) {
throw new CommandFailedException("Unable to mount Filesystem", e); throw new VolumeException("Unable to mount Filesystem", e);
} }
} }
@Override @Override
public void reveal() throws CommandFailedException { public void reveal() throws VolumeException {
try { try {
fuseMnt.revealInFileManager(); fuseMnt.revealInFileManager();
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { } catch (CommandFailedException e) {
LOG.info("Revealing the vault in file manger failed: " + e.getMessage()); LOG.info("Revealing the vault in file manger failed: " + e.getMessage());
throw new CommandFailedException(e); throw new VolumeException(e);
} }
} }
@Override @Override
public synchronized void unmount() throws CommandFailedException { public synchronized void unmount() throws VolumeException {
try { try {
fuseMnt.close(); fuseMnt.close();
} catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { } catch (CommandFailedException e) {
throw new CommandFailedException(e); throw new VolumeException(e);
} }
cleanup();
} }
@Override private void cleanup() {
public synchronized void unmountForced() throws CommandFailedException {
unmount();
}
@Override
public void stop() {
if (extraDirCreated) { if (extraDirCreated) {
try { try {
Files.delete(mountPath); Files.delete(mountPath);
@@ -126,19 +117,9 @@ public class FuseVolume implements Volume {
} }
} }
@Override
public String getMountUri() {
return "";
}
@Override @Override
public boolean isSupported() { public boolean isSupported() {
return FuseMountFactory.isFuseSupported(); return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
}
@Override
public boolean supportsForcedUnmount() {
return false;
} }
} }
@@ -19,16 +19,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
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 javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.Observable; import javafx.beans.Observable;
@@ -39,6 +30,15 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils; 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.fxmisc.easybind.EasyBind;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -53,20 +53,21 @@ public class Vault {
private final Settings settings; private final Settings settings;
private final VaultSettings vaultSettings; private final VaultSettings vaultSettings;
private final Provider<Volume> volumeProvider;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>(); private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED); private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
private Volume volume; private Volume volume;
public enum State { public enum State {
LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING LOCKED, PROCESSING, UNLOCKED
} }
@Inject @Inject
Vault(Settings settings, VaultSettings vaultSettings, Volume volume) { Vault(Settings settings, VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
this.settings = settings; this.settings = settings;
this.vaultSettings = vaultSettings; this.vaultSettings = vaultSettings;
this.volume = volume; this.volumeProvider = volumeProvider;
} }
// ****************************************************************************** // ******************************************************************************
@@ -98,51 +99,37 @@ public class Vault {
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase); CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
} }
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException { public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
CryptoFileSystem fs = getCryptoFileSystem(passphrase); Platform.runLater(() -> state.set(State.PROCESSING));
volume.prepare(fs); try {
Platform.runLater(() -> { CryptoFileSystem fs = getCryptoFileSystem(passphrase);
state.set(State.UNLOCKED); 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(() -> { Platform.runLater(() -> {
state.set(State.MOUNTING); state.set(State.PROCESSING);
});
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);
}); });
if (forced && volume.supportsForcedUnmount()) { if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced(); volume.unmountForced();
} else { } else {
volume.unmount(); volume.unmount();
} }
Platform.runLater(() -> {
state.set(State.UNLOCKED);
});
}
public synchronized void lock() throws IOException {
volume.stop();
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null); CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
if (fs != null) { if (fs != null) {
fs.close(); try {
fs.close();
} catch (IOException e) {
LOG.error("Error closing file system.", e);
}
} }
Platform.runLater(() -> { Platform.runLater(() -> {
state.set(State.LOCKED); state.set(State.LOCKED);
@@ -154,26 +141,21 @@ public class Vault {
*/ */
public void prepareForShutdown() { public void prepareForShutdown() {
try { try {
unmount(); lock(false);
} catch (CommandFailedException e) { } catch (Volume.VolumeException e) {
if (volume.supportsForcedUnmount()) { if (volume.supportsForcedUnmount()) {
try { try {
unmountForced(); lock(true);
} catch (CommandFailedException e1) { } catch (Volume.VolumeException e1) {
LOG.warn("Failed to force unmount vault."); LOG.warn("Failed to force lock vault.", e1);
} }
} else { } 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(); volume.reveal();
} }
@@ -289,10 +271,6 @@ public class Vault {
} }
} }
public String getFilesystemRootUrl() {
return volume.getMountUri();
}
public String getId() { public String getId() {
return vaultSettings.getId(); return vaultSettings.getId();
} }
@@ -18,10 +18,13 @@ import org.cryptomator.common.settings.VaultSettings;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Module @Module
public class VaultModule { public class VaultModule {
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
private final VaultSettings vaultSettings; private final VaultSettings vaultSettings;
public VaultModule(VaultSettings vaultSettings) { public VaultModule(VaultSettings vaultSettings) {
@@ -42,21 +45,18 @@ public class VaultModule {
} }
@Provides @Provides
@PerVault public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
public Volume provideNioAdpater(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume) { VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
VolumeImpl impl = settings.volumeImpl().get(); if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
switch (impl) { return dokanyVolume;
case FUSE: } else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
if (fuseVolume.isSupported()) { return fuseVolume;
return fuseVolume; } else {
} else { if (VolumeImpl.WEBDAV != preferredImpl) {
settings.volumeImpl().set(VolumeImpl.WEBDAV); LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
// fallthrough to WEBDAV }
} assert webDavVolume.isSupported();
case WEBDAV: return webDavVolume;
return webDavVolume;
default:
throw new IllegalStateException("Unsupported NioAdapter: " + impl);
} }
} }
@@ -9,30 +9,50 @@ import java.io.IOException;
*/ */
public interface Volume { 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 { void reveal() throws VolumeException;
throw new CommandFailedException("Not implemented.");
}
void unmount() throws CommandFailedException; void unmount() throws VolumeException;
default void unmountForced() throws CommandFailedException { // optional forced unmounting:
throw new CommandFailedException("Operation not supported.");
}
void stop();
String getMountUri();
default boolean isSupported() {
return false;
}
default boolean supportsForcedUnmount() { default boolean supportsForcedUnmount() {
return false; 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);
}
}
} }
@@ -15,7 +15,6 @@ import javax.inject.Provider;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@VaultModule.PerVault
public class WebDavVolume implements Volume { public class WebDavVolume implements Volume {
private static final String LOCALHOST_ALIAS = "cryptomator-vault"; private static final String LOCALHOST_ALIAS = "cryptomator-vault";
@@ -36,7 +35,7 @@ public class WebDavVolume implements Volume {
} }
@Override @Override
public void prepare(CryptoFileSystem fs) { public void mount(CryptoFileSystem fs) throws VolumeException {
if (server == null) { if (server == null) {
server = serverProvider.get(); server = serverProvider.get();
} }
@@ -45,10 +44,10 @@ public class WebDavVolume implements Volume {
} }
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
servlet.start(); servlet.start();
mount();
} }
@Override private void mount() throws VolumeException {
public void mount() throws CommandFailedException {
if (servlet == null) { if (servlet == null) {
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); 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 this.mount = servlet.mount(mountParams); // might block this thread for a while
} catch (Mounter.CommandFailedException e) { } catch (Mounter.CommandFailedException e) {
e.printStackTrace(); e.printStackTrace();
throw new CommandFailedException(e); throw new VolumeException(e);
} }
} }
@Override @Override
public void reveal() throws CommandFailedException { public void reveal() throws VolumeException {
try { try {
mount.reveal(); mount.reveal();
} catch (Mounter.CommandFailedException e) { } catch (Mounter.CommandFailedException e) {
e.printStackTrace(); e.printStackTrace();
throw new CommandFailedException(e); throw new VolumeException(e);
} }
} }
@Override @Override
public synchronized void unmount() throws CommandFailedException { public synchronized void unmount() throws VolumeException {
try { try {
mount.unmount(); mount.unmount();
} catch (Mounter.CommandFailedException e) { } catch (Mounter.CommandFailedException e) {
throw new CommandFailedException(e); throw new VolumeException(e);
} }
cleanup();
} }
@Override @Override
public synchronized void unmountForced() { public synchronized void unmountForced() throws VolumeException {
mount.forced(); try {
mount.forced().orElseThrow(IllegalStateException::new).unmount();
} catch (Mounter.CommandFailedException e) {
throw new VolumeException(e);
}
cleanup();
} }
private String getLocalhostAliasOrNull() { private String getLocalhostAliasOrNull() {
@@ -102,28 +107,19 @@ public class WebDavVolume implements Volume {
} }
} }
@Override private void cleanup() {
public void stop() {
if (servlet != null) { if (servlet != null) {
servlet.stop(); servlet.stop();
} }
} }
public synchronized String getMountUri() {
return servlet.getServletRootUri().toString() + "/";
}
/**
* TODO: what to check wether it is implemented?
*
* @return
*/
@Override @Override
public boolean isSupported() { public boolean isSupported() {
return true; return true;
} }
@Override
public boolean supportsForcedUnmount() { public boolean supportsForcedUnmount() {
return mount != null && mount.forced().isPresent(); return mount != null && mount.forced().isPresent();
} }
@@ -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();
}
}
@@ -28,6 +28,8 @@ import javafx.scene.paint.Color;
@Singleton @Singleton
public class PasswordStrengthUtil { 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 Zxcvbn zxcvbn;
private final List<String> sanitizedInputs; private final List<String> sanitizedInputs;
private final Localization localization; private final Localization localization;
@@ -43,10 +45,9 @@ public class PasswordStrengthUtil {
public int computeRate(String password) { public int computeRate(String password) {
if (Strings.isNullOrEmpty(password)) { if (Strings.isNullOrEmpty(password)) {
return -1; return -1;
} else if (password.length() > 100) {
return 4; // assume this is strong. zxcvbn memory and runtime depends vastly on the password length
} else { } 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();
} }
} }
@@ -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;
}
}
+12 -15
View File
@@ -75,28 +75,25 @@
<!-- Row 3.2 --> <!-- Row 3.2 -->
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" /> <CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
<!-- Row 3.3 --> <!-- 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 --> <!-- Row 3.4 -->
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.mountName" 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" />
<TextField GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.5 --> <!-- Row 3.5 Alt1 -->
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" /> <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 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.6 Alt2 --> <!-- Row 3.5 Alt2 -->
<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="useOwnMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" /> <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"> <HBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="mountPathBox" spacing="6.0">
<TextField GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="mountPath" cacheShape="true" cache="true" /> <TextField fx:id="mountPath" cacheShape="true" cache="true" />
<Button text="%unlock.label.mountPathButton" fx:id="changeMountPathButton" onAction="#didClickchangeMountPathButton"/> <Button text="%unlock.label.mountPathButton" fx:id="changeMountPathButton" onAction="#didClickchangeMountPathButton"/>
</HBox> </HBox>
@@ -28,18 +28,9 @@
<fx:define> <fx:define>
<ContextMenu fx:id="moreOptionsMenu"> <ContextMenu fx:id="moreOptionsMenu">
<items> <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"> <MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic> <graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
</MenuItem> </MenuItem>
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl">
<graphic><Label text="&#xf376;" styleClass="ionicons"/></graphic>
</MenuItem>
</items> </items>
</ContextMenu> </ContextMenu>
</fx:define> </fx:define>
@@ -23,6 +23,9 @@ main.createVault.nonEmptyDir.content=The selected directory already contains fil
# welcome.fxml # welcome.fxml
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates... welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
welcome.newVersionMessage=Version %1$s can be downloaded.\nThis is %2$s. 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.fxml
initialize.label.password=Password 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.fxml
unlock.label.password=Password unlock.label.password=Password
unlock.label.savePassword=Save Password unlock.label.savePassword=Save Password
unlock.label.mountAfterUnlock=Mount Drive
unlock.label.mountName=Drive Name unlock.label.mountName=Drive Name
unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental) unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
unlock.label.revealAfterMount=Reveal Drive unlock.label.revealAfterMount=Reveal Drive
@@ -100,10 +102,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed
# unlocked.fxml # unlocked.fxml
unlocked.button.lock=Lock Vault unlocked.button.lock=Lock Vault
unlocked.moreOptions.mount=Mount Drive
unlocked.moreOptions.unmount=Eject Drive
unlocked.moreOptions.reveal=Reveal Drive unlocked.moreOptions.reveal=Reveal Drive
unlocked.moreOptions.copyUrl=Copy Filesystem URL
unlocked.label.mountFailed=Connecting drive failed unlocked.label.mountFailed=Connecting drive failed
unlocked.label.revealFailed=Command failed unlocked.label.revealFailed=Command failed
unlocked.label.unmountFailed=Ejecting drive failed unlocked.label.unmountFailed=Ejecting drive failed
@@ -123,9 +122,10 @@ settings.webdav.port.apply=Apply
settings.webdav.prefGvfsScheme.label=WebDAV Scheme settings.webdav.prefGvfsScheme.label=WebDAV Scheme
settings.debugMode.label=Debug Mode * settings.debugMode.label=Debug Mode *
settings.requiresRestartLabel=* Cryptomator needs to restart settings.requiresRestartLabel=* Cryptomator needs to restart
settings.volume.label= Mount-Methode * settings.volume.label=Preferred Volume Type
settings.volume.webdav=WebDAV settings.volume.webdav=WebDAV
settings.volume.fuse=FUSE settings.volume.fuse=FUSE
settings.volume.dokany=Dokany
# tray icon # tray icon
tray.menu.open=Open 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

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