diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 93% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index 4889efd2a..bb48037de 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,7 +4,7 @@ - Ensure you're running the latest version of Cryptomator. - Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively. -- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/faq). +- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq). - If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new). ## Did you write a patch that fixes a bug? @@ -18,7 +18,7 @@ ## Code of Conduct -Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/CODE_OF_CONDUCT.md). +Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md). ## Above all, thank you for your contributions diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 92% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md index f0908e880..3808d9c0d 100644 --- a/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,8 +4,8 @@ Before creating a new issue make sure that you - searched existing (and closed) issues: https://github.com/cryptomator/cryptomator/issues - searched the knowledge base: https://community.cryptomator.org/c/kb - have read the support guide: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md -- have read the contribution guide: https://github.com/cryptomator/cryptomator/blob/develop/CONTRIBUTING.md -- have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/CODE_OF_CONDUCT.md +- have read the contribution guide: https://github.com/cryptomator/cryptomator/blob/develop/.github/CONTRIBUTING.md +- have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md ## Basic Info diff --git a/.github/ISSUE_TEMPLATE/1.4.0-beta-testing.md b/.github/ISSUE_TEMPLATE/1.4.0-beta-testing.md new file mode 100644 index 000000000..c78c19e22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.4.0-beta-testing.md @@ -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? diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 000000000..090694a5b --- /dev/null +++ b/.github/no-response.yml @@ -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. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..79663c203 --- /dev/null +++ b/.github/stale.yml @@ -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 diff --git a/.travis.yml b/.travis.yml index 5b65484a0..8d9c59e8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: java sudo: false jdk: -- oraclejdk9 +- oraclejdk10 branches: except: - continuous # To avoid infinite loops, as this tag is created by this Travis config @@ -14,6 +14,7 @@ env: - secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY - secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN - secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY + - secure: "PiH/o9MLOyPdjIwECIEfj3TuUxx7QB0CIs3o1WXjqb1PbDdHDbQyvswYit6xDw9NrJp/A+ov2k00jq+n+8fLTSd4AY21y5WiJN/ccCTWUuUiFhGxOyM37aeWAPAn4rUp7D7o8jLxEdpGZAfglIzaz+GCEQYxfV/w3FDwztViXgQ=" # GPG_PASSPHRASE addons: apt: packages: @@ -28,13 +29,8 @@ install: - curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml - mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 script: -- mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage -after_success: -- jdk_switcher use oraclejdk8 -- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/2.0.1/codacy-coverage-reporter-2.0.1-assembly.jar -- $JAVA_HOME/bin/java -cp ~/codacy-coverage-reporter-assembly-latest.jar com.codacy.CodacyCoverageReporter -l Java -r main/jacoco-report/target/site/jacoco-aggregate/jacoco.xml +- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage before_deploy: -- jdk_switcher use oraclejdk9 - | if [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then CONTINUOUS_RELEASE_URL=`curl -s https://api.github.com/repos/cryptomator/cryptomator/releases/tags/continuous | jq -re '.url'` @@ -54,6 +50,8 @@ before_deploy: git remote remove gh fi - mvn -fmain/pom.xml clean package -Prelease -DskipTests +- gpg --import 34C80F11.gpg +- gpg --detach-sign -a -u 34C80F11 --batch --passphrase ${GPG_PASSPHRASE} main/ant-kit/target/antkit.zip deploy: - provider: releases # CONTINUOUS prerelease: true @@ -63,7 +61,8 @@ deploy: file_glob: true file: - "main/uber-jar/target/Cryptomator-*.jar" - - "main/ant-kit/target/antkit.tar.gz" + - "main/ant-kit/target/antkit.zip" + - "main/ant-kit/target/antkit.zip.asc" skip_cleanup: true name: Cryptomator continuous build body: Automatically built on $(date +'%F %T %Z'). @@ -76,7 +75,8 @@ deploy: api_key: $GITHUB_API_KEY file: - "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar" - - "main/ant-kit/target/antkit.tar.gz" + - "main/ant-kit/target/antkit.zip" + - "main/ant-kit/target/antkit.zip.asc" skip_cleanup: true on: repo: cryptomator/cryptomator @@ -91,3 +91,11 @@ deploy: on: repo: cryptomator/cryptomator tags: true +after_script: +- jdk_switcher use oraclejdk8 +- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.1/codacy-coverage-reporter-4.0.1-assembly.jar +- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial +- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial +- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial +- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial +- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter-assembly-latest.jar final diff --git a/34C80F11.gpg b/34C80F11.gpg new file mode 100644 index 000000000..e40ebc7fe --- /dev/null +++ b/34C80F11.gpg @@ -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----- \ No newline at end of file diff --git a/README.md b/README.md index 1fe176f9a..dc3912b94 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,7 @@ For more information on the security details visit [cryptomator.org](https://cry ### Dependencies -* Java 8 (min. 8u51, we recommend to use the current version) -* [JCE unlimited strength policy files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) (needed for 256-bit keys) +* Java 10 (min. 10.0.1, we recommend to use the current version) * Maven 3 * Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers)) diff --git a/main/ant-kit/assembly.xml b/main/ant-kit/assembly.xml index 182a1b654..661711fe4 100644 --- a/main/ant-kit/assembly.xml +++ b/main/ant-kit/assembly.xml @@ -4,7 +4,7 @@ tarball false - tar.gz + zip diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml index 83401f36b..24c4898d1 100644 --- a/main/ant-kit/pom.xml +++ b/main/ant-kit/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 ant-kit pom @@ -73,7 +73,7 @@ - + maven-assembly-plugin 3.1.0 diff --git a/main/ant-kit/src/main/resources/build.xml b/main/ant-kit/src/main/resources/build.xml index aae41cbe3..7ed445448 100644 --- a/main/ant-kit/src/main/resources/build.xml +++ b/main/ant-kit/src/main/resources/build.xml @@ -4,9 +4,14 @@ + + + + + - + @@ -21,27 +26,26 @@ - + - - - - + + + + + - - - + diff --git a/main/ant-kit/src/main/resources/logback.xml b/main/ant-kit/src/main/resources/logback.xml deleted file mode 100644 index 1682a356f..000000000 --- a/main/ant-kit/src/main/resources/logback.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - ${user.home}/.Cryptomator/cryptomator.log - false - - ${user.home}/.Cryptomator/cryptomator%i.log - 0 - 9 - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - ${user.home}/.Cryptomator/upgrade.log - true - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - - - - diff --git a/main/commons/pom.xml b/main/commons/pom.xml index c6097c3be..b3223aa0c 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 commons Cryptomator Commons diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index bd32a1cb6..7f57122a3 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java @@ -8,39 +8,34 @@ ******************************************************************************/ package org.cryptomator.common.settings; -import java.util.function.Consumer; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.*; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import java.util.function.Consumer; + public class Settings { public static final int MIN_PORT = 1024; public static final int MAX_PORT = 65535; - public static final boolean DEFAULT_CHECK_FOR_UDPATES = true; + public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false; + public static final boolean DEFAULT_CHECK_FOR_UDPATES = false; public static final int DEFAULT_PORT = 42427; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final String DEFAULT_GVFS_SCHEME = "dav"; public static final boolean DEFAULT_DEBUG_MODE = false; - public static final VolumeImpl DEFAULT_VOLUME_IMPL = VolumeImpl.FUSE; + public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE; private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); + private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK); private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES); private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT); private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS); private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME); private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); - private final ObjectProperty volumeImpl = new SimpleObjectProperty<>(DEFAULT_VOLUME_IMPL); + private final ObjectProperty preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL); private Consumer saveCmd; @@ -49,12 +44,13 @@ public class Settings { */ Settings() { directories.addListener((ListChangeListener.Change change) -> this.save()); + askedForUpdateCheck.addListener(this::somethingChanged); checkForUpdates.addListener(this::somethingChanged); port.addListener(this::somethingChanged); numTrayNotifications.addListener(this::somethingChanged); preferredGvfsScheme.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged); - volumeImpl.addListener(this::somethingChanged); + preferredVolumeImpl.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { @@ -77,6 +73,10 @@ public class Settings { return directories; } + public BooleanProperty askedForUpdateCheck() { + return askedForUpdateCheck; + } + public BooleanProperty checkForUpdates() { return checkForUpdates; } @@ -97,8 +97,8 @@ public class Settings { return debugMode; } - public ObjectProperty volumeImpl() { - return volumeImpl; + public ObjectProperty preferredVolumeImpl() { + return preferredVolumeImpl; } } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java index 00dc439f7..2ece80909 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java @@ -5,17 +5,16 @@ *******************************************************************************/ package org.cryptomator.common.settings; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class SettingsJsonAdapter extends TypeAdapter { @@ -28,12 +27,13 @@ public class SettingsJsonAdapter extends TypeAdapter { out.beginObject(); out.name("directories"); writeVaultSettingsArray(out, value.getDirectories()); + out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get()); out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get()); out.name("port").value(value.port().get()); out.name("numTrayNotifications").value(value.numTrayNotifications().get()); out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get()); out.name("debugMode").value(value.debugMode().get()); - out.name("volumeImpl").value(value.volumeImpl().get().name()); + out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name()); out.endObject(); } @@ -56,6 +56,9 @@ public class SettingsJsonAdapter extends TypeAdapter { case "directories": settings.getDirectories().addAll(readVaultSettingsArray(in)); break; + case "askedForUpdateCheck": + settings.askedForUpdateCheck().set(in.nextBoolean()); + break; case "checkForUpdatesEnabled": settings.checkForUpdates().set(in.nextBoolean()); break; @@ -71,8 +74,8 @@ public class SettingsJsonAdapter extends TypeAdapter { case "debugMode": settings.debugMode().set(in.nextBoolean()); break; - case "volumeImpl": - settings.volumeImpl().set(parseNioAdapterName(in.nextString())); + case "preferredVolumeImpl": + settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString())); break; default: LOG.warn("Unsupported vault setting found in JSON: " + name); @@ -84,11 +87,11 @@ public class SettingsJsonAdapter extends TypeAdapter { return settings; } - private VolumeImpl parseNioAdapterName(String nioAdapterName) { + private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) { try { return VolumeImpl.valueOf(nioAdapterName); } catch (IllegalArgumentException e) { - return Settings.DEFAULT_VOLUME_IMPL; + return Settings.DEFAULT_PREFERRED_VOLUME_IMPL; } } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java index f6a0aa805..1f8510d99 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java @@ -26,7 +26,6 @@ import javafx.beans.property.StringProperty; public class VaultSettings { public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false; - public static final boolean DEFAULT_MOUNT_AFTER_UNLOCK = true; public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true; public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false; @@ -35,7 +34,6 @@ public class VaultSettings { private final StringProperty mountName = new SimpleStringProperty(); private final StringProperty winDriveLetter = new SimpleStringProperty(); private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); - private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK); private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT); private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); private final StringProperty individualMountPath = new SimpleStringProperty(); @@ -47,7 +45,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount, usesIndividualMountPath, individualMountPath}; + return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath}; } private void deriveMountNameFromPath(Path path) { @@ -118,10 +116,6 @@ public class VaultSettings { return unlockAfterStartup; } - public BooleanProperty mountAfterUnlock() { - return mountAfterUnlock; - } - public BooleanProperty revealAfterMount() { return revealAfterMount; } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java index f63ab3c33..5fb30ef88 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java @@ -25,7 +25,6 @@ class VaultSettingsJsonAdapter { out.name("mountName").value(value.mountName().get()); out.name("winDriveLetter").value(value.winDriveLetter().get()); out.name("unlockAfterStartup").value(value.unlockAfterStartup().get()); - out.name("mountAfterUnlock").value(value.mountAfterUnlock().get()); out.name("revealAfterMount").value(value.revealAfterMount().get()); out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get()); //TODO: should this always be written? ( because it could contain metadata, which the user does not want to save!) @@ -40,7 +39,6 @@ class VaultSettingsJsonAdapter { String individualMountPath = null; String winDriveLetter = null; boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP; - boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK; boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; @@ -63,9 +61,6 @@ class VaultSettingsJsonAdapter { case "unlockAfterStartup": unlockAfterStartup = in.nextBoolean(); break; - case "mountAfterUnlock": - mountAfterUnlock = in.nextBoolean(); - break; case "revealAfterMount": revealAfterMount = in.nextBoolean(); break; @@ -87,7 +82,6 @@ class VaultSettingsJsonAdapter { vaultSettings.path().set(Paths.get(path)); vaultSettings.winDriveLetter().set(winDriveLetter); vaultSettings.unlockAfterStartup().set(unlockAfterStartup); - vaultSettings.mountAfterUnlock().set(mountAfterUnlock); vaultSettings.revealAfterMount().set(revealAfterMount); vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath); vaultSettings.individualMountPath().set(individualMountPath); diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java b/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java index 0862c499b..634f528f4 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java @@ -4,7 +4,8 @@ import java.util.Arrays; public enum VolumeImpl { WEBDAV("WebDAV"), - FUSE("FUSE"); + FUSE("FUSE"), + DOKANY("DOKANY"); private String displayName; diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java index 511197d29..cee09295a 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java @@ -22,7 +22,7 @@ public class SettingsJsonAdapterTest { + "\"checkForUpdatesEnabled\": true,"// + "\"port\": 8080,"// + "\"numTrayNotifications\": 42,"// - + "\"volumeImpl\": \"FUSE\"}"; + + "\"preferredVolumeImpl\": \"FUSE\"}"; Settings settings = adapter.fromJson(json); @@ -31,7 +31,7 @@ public class SettingsJsonAdapterTest { Assert.assertEquals(8080, settings.port().get()); Assert.assertEquals(42, settings.numTrayNotifications().get()); Assert.assertEquals("dav", settings.preferredGvfsScheme().get()); - Assert.assertEquals(VolumeImpl.FUSE, settings.volumeImpl().get()); + Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get()); } } diff --git a/main/jacoco-report/.gitignore b/main/jacoco-report/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/jacoco-report/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml deleted file mode 100644 index cd65ba3dd..000000000 --- a/main/jacoco-report/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - org.cryptomator - main - 1.4.0-beta1 - - jacoco-report - Cryptomator Code Coverage Report - pom - - - - - org.cryptomator - commons - - - org.cryptomator - keychain - - - org.cryptomator - ui - - - org.cryptomator - launcher - - - - - - - org.jacoco - jacoco-maven-plugin - - - report-aggregate - verify - - report-aggregate - - - - - - - diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index 084fafbf0..211a29688 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 keychain System Keychain Access diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index ca4a8bfac..a3e8ca84b 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 launcher Cryptomator Launcher diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java index 171447c6c..8a97e336d 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -37,6 +37,8 @@ public class Cryptomator { } } catch (IOException e) { LOG.error("Failed to initiate inter-process communication.", e); + } catch (Throwable e) { + LOG.error("Error during startup", e); } System.exit(0); // end remaining non-daemon threads. } diff --git a/main/pom.xml b/main/pom.xml index 7ff14adfd..f909bfe57 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 pom Cryptomator @@ -26,25 +26,26 @@ 1.2.0 1.5.2 - 1.0.4 2.0.0 - 0.1.4 + 0.1.5 + 0.1.2 + 1.0.4 2.5 3.6 1.0.3 - - 24.1-jre - 2.14.1 - 2.8.2 - + + 25.1-jre + 2.16 + 2.8.5 + 1.7.25 1.2.3 - + 4.12 4.12.1 - 2.11.0 + 2.19.0 1.3 @@ -59,6 +60,10 @@ true + + jcenter + http://jcenter.bintray.com + @@ -101,6 +106,11 @@ fuse-nio-adapter ${cryptomator.fuse.version} + + org.cryptomator + dokany-nio-adapter + ${cryptomator.dokany.version} + org.cryptomator webdav-nio-adapter @@ -245,9 +255,6 @@ coverage - - jacoco-report - @@ -289,6 +296,12 @@ prepare-agent + + report + + report + + @@ -304,9 +317,9 @@ maven-compiler-plugin 3.7.0 - 9 - 9 - 9 + 10 + 10 + 10 --add-modules jdk.incubator.httpclient diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml index 8e9da7d37..6b87edde7 100644 --- a/main/uber-jar/pom.xml +++ b/main/uber-jar/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 uber-jar Single über jar with all dependencies diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 4b3f8098a..96f74d750 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -4,35 +4,40 @@ org.cryptomator main - 1.4.0-beta1 + 1.4.0-beta2 ui Cryptomator GUI + + org.cryptomator + keychain + org.cryptomator commons + org.cryptomator cryptofs - - org.cryptomator - webdav-nio-adapter - org.cryptomator jni org.cryptomator - keychain + fuse-nio-adapter org.cryptomator - fuse-nio-adapter + dokany-nio-adapter + + + org.cryptomator + webdav-nio-adapter diff --git a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java index 6694c21b4..eca371bbf 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java @@ -53,6 +53,7 @@ public class ExitUtil { private final Localization localization; private final Settings settings; private final Optional macFunctions; + private TrayIcon trayIcon; @Inject public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional macFunctions) { @@ -82,7 +83,7 @@ public class ExitUtil { } private void initTrayIconExitHandler(Runnable exitCommand) { - final TrayIcon trayIcon = createTrayIcon(exitCommand); + trayIcon = createTrayIcon(exitCommand); try { // double clicking tray icon should open Cryptomator if (SystemUtils.IS_OS_WINDOWS) { @@ -118,14 +119,7 @@ public class ExitUtil { exitItem.addActionListener(e -> exitCommand.run()); popup.add(exitItem); - final Image image; - if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) { - image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_white.png")); - } else if (SystemUtils.IS_OS_MAC_OSX) { - image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_black.png")); - } else { - image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon.png")); - } + final Image image = getAppropriateTrayIconImage(true); return new TrayIcon(image, localization.getString("app.name"), popup); } @@ -202,4 +196,23 @@ public class ExitUtil { }); } + public void updateTrayIcon(boolean areAllVaultsLocked) { + if (trayIcon != null) { + Image image = getAppropriateTrayIconImage(areAllVaultsLocked); + trayIcon.setImage(image); + } + } + + private Image getAppropriateTrayIconImage(boolean areAllVaultsLocked) { + String resourceName; + if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) { + resourceName = areAllVaultsLocked ? "/tray_icon_mac_white.png" : "/tray_icon_unlocked_mac_white.png"; + } else if (SystemUtils.IS_OS_MAC_OSX) { + resourceName = areAllVaultsLocked ? "/tray_icon_mac_black.png" : "/tray_icon_unlocked_mac_black.png"; + } else { + resourceName = areAllVaultsLocked ? "/tray_icon.png" : "/tray_icon_unlocked.png"; + } + return Toolkit.getDefaultToolkit().getImage(getClass().getResource(resourceName)); + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/UiModule.java b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java index 4bcc9b877..8b94936b0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UiModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java @@ -11,6 +11,8 @@ package org.cryptomator.ui; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import javax.inject.Named; @@ -32,16 +34,38 @@ import org.fxmisc.easybind.EasyBind; @Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class}) public class UiModule { + private static final int NUM_SCHEDULER_THREADS = 4; + @Provides @Singleton Settings provideSettings(SettingsProvider settingsProvider) { return settingsProvider.get(); } + @Provides + @Singleton + ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) { + final AtomicInteger threadNumber = new AtomicInteger(1); + ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> { + Thread t = new Thread(r); + t.setName("Scheduler Thread " + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + }); + shutdownTaskScheduler.accept(executorService::shutdown); + return executorService; + } + @Provides @Singleton ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) { - ExecutorService executorService = Executors.newCachedThreadPool(); + final AtomicInteger threadNumber = new AtomicInteger(1); + ExecutorService executorService = Executors.newCachedThreadPool(r -> { + Thread t = new Thread(r); + t.setName("Background Thread " + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + }); shutdownTaskScheduler.accept(executorService::shutdown); return executorService; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index 0f6010eec..e0e4d70e5 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -125,6 +125,7 @@ public class MainController implements ViewController { this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy); this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED)); + EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon); EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit); autoUnlocker.unlockAllSilently(); @@ -395,7 +396,7 @@ public class MainController implements ViewController { return; } if (newValue.getState() != Vault.State.LOCKED) { - this.showUnlockedView(newValue); + this.showUnlockedView(newValue, false); } else if (!newValue.doesVaultDirectoryExist()) { this.showNotFoundView(); } else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) { @@ -494,22 +495,25 @@ public class MainController implements ViewController { public void didUnlock(Vault vault) { if (vault.equals(selectedVault.getValue())) { - this.showUnlockedView(vault); + this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue()); } } - private void showUnlockedView(Vault vault) { - final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> { - return viewControllerLoader.load("/fxml/unlocked.fxml"); - }); + private void showUnlockedView(Vault vault, boolean reveal) { + final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml")); ctrl.setVault(vault); ctrl.setListener(this::didLock); + if (reveal) { + ctrl.revealVault(vault); + } activeController.set(ctrl); } public void didLock(UnlockedController ctrl) { unlockedVaults.remove(ctrl.getVault()); - showUnlockView(UnlockController.State.UNLOCKING); + if (ctrl.getVault().getId() == selectedVault.get().getId()) { + showUnlockView(UnlockController.State.UNLOCKING); + } activeController.get().focus(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java index f4b997314..0cc884943 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java @@ -77,9 +77,6 @@ public class SettingsController implements ViewController { @FXML private ChoiceBox prefGvfsScheme; - @FXML - private Label volumeLabel; - @FXML private ChoiceBox volume; @@ -97,8 +94,7 @@ public class SettingsController implements ViewController { //NIOADAPTER volume.getItems().addAll(getSupportedAdapters()); - volume.setValue(settings.volumeImpl().get()); - volume.setVisible(true); + volume.setValue(settings.preferredVolumeImpl().get()); volume.setConverter(new NioAdapterImplStringConverter()); //WEBDAV @@ -127,13 +123,13 @@ public class SettingsController implements ViewController { settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty()); settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty()); - settings.volumeImpl().bind(volume.valueProperty()); + settings.preferredVolumeImpl().bind(volume.valueProperty()); settings.debugMode().bind(debugModeCheckbox.selectedProperty()); } - //TODO: how to implement this? private VolumeImpl[] getSupportedAdapters() { - return new VolumeImpl[]{VolumeImpl.FUSE, VolumeImpl.WEBDAV}; + // TODO: filter depending on supported drivers + return VolumeImpl.values(); } @Override diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index fe2c2ca90..2001e50b6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -8,41 +8,21 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import java.io.IOException; -import java.nio.file.*; +import javax.inject.Inject; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; import java.util.Optional; - -import javax.inject.Inject; - -import javafx.beans.binding.Bindings; -import javafx.scene.layout.HBox; -import org.apache.commons.lang3.CharUtils; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.settings.VolumeImpl; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.VaultSettings; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; -import org.cryptomator.frontend.webdav.ServerLifecycleException; -import org.cryptomator.keychain.KeychainAccess; -import org.cryptomator.ui.controls.SecPasswordField; -import org.cryptomator.ui.l10n.Localization; -import org.cryptomator.ui.model.Vault; -import org.cryptomator.ui.model.WindowsDriveLetters; -import org.cryptomator.ui.util.AsyncTaskService; -import org.cryptomator.ui.util.DialogBuilderUtil; -import org.fxmisc.easybind.EasyBind; -import org.fxmisc.easybind.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.concurrent.ExecutorService; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; - import javafx.application.Application; +import javafx.beans.binding.Bindings; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; @@ -59,8 +39,28 @@ import javafx.scene.control.ProgressIndicator; import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; import javafx.scene.text.Text; import javafx.util.StringConverter; +import org.apache.commons.lang3.CharUtils; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.frontend.webdav.ServerLifecycleException; +import org.cryptomator.keychain.KeychainAccess; +import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; +import org.cryptomator.ui.model.WindowsDriveLetters; +import org.cryptomator.ui.util.DialogBuilderUtil; +import org.cryptomator.ui.util.Tasks; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class UnlockController implements ViewController { @@ -73,23 +73,23 @@ public class UnlockController implements ViewController { private final Application app; private final Localization localization; - private final AsyncTaskService asyncTaskService; private final WindowsDriveLetters driveLetters; private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange; private final Optional keychainAccess; private final Settings settings; + private final ExecutorService executor; private Vault vault; private Optional listener = Optional.empty(); private Subscription vaultSubs = Subscription.EMPTY; @Inject - public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional keychainAccess, Settings settings) { + public UnlockController(Application app, Localization localization, WindowsDriveLetters driveLetters, Optional keychainAccess, Settings settings, ExecutorService executor) { this.app = app; this.localization = localization; - this.asyncTaskService = asyncTaskService; this.driveLetters = driveLetters; this.keychainAccess = keychainAccess; this.settings = settings; + this.executor = executor; } @FXML @@ -107,9 +107,6 @@ public class UnlockController implements ViewController { @FXML private CheckBox savePassword; - @FXML - private CheckBox mountAfterUnlock; - @FXML private TextField mountName; @@ -159,8 +156,6 @@ public class UnlockController implements ViewController { public void initialize() { advancedOptions.managedProperty().bind(advancedOptions.visibleProperty()); unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty()); - mountName.disableProperty().bind(mountAfterUnlock.selectedProperty().not()); - revealAfterMount.disableProperty().bind(mountAfterUnlock.selectedProperty().not()); mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents); mountName.textProperty().addListener(this::mountNameDidChange); savePassword.setDisable(!keychainAccess.isPresent()); @@ -186,7 +181,7 @@ public class UnlockController implements ViewController { winDriveLetterLabel.setManaged(false); winDriveLetter.setVisible(false); winDriveLetter.setManaged(false); - if (VolumeImpl.WEBDAV.equals(settings.volumeImpl().get())) { + if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) { useOwnMountPath.setVisible(false); useOwnMountPath.setManaged(false); mountPathLabel.setManaged(false); @@ -250,12 +245,10 @@ public class UnlockController implements ViewController { } VaultSettings vaultSettings = vault.getVaultSettings(); unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get()); - mountAfterUnlock.setSelected(vaultSettings.mountAfterUnlock().get()); revealAfterMount.setSelected(vaultSettings.revealAfterMount().get()); useOwnMountPath.setSelected(vaultSettings.usesIndividualMountPath().get()); vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set)); - vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), vaultSettings.mountAfterUnlock()::set)); vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set)); vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set)); @@ -430,7 +423,7 @@ public class UnlockController implements ViewController { progressIndicator.setVisible(true); CharSequence password = passwordField.getCharacters(); - asyncTaskService.asyncTaskOf(() -> { + Tasks.create(() -> { vault.unlock(password); if (keychainAccess.isPresent() && savePassword.isSelected()) { keychainAccess.get().storePassphrase(vault.getId(), password); @@ -457,16 +450,16 @@ public class UnlockController implements ViewController { }).onError(ServerLifecycleException.class, e -> { LOG.error("Unlock failed for technical reasons.", e); messageText.setText(localization.getString("unlock.errorMessage.unlockFailed")); - }).onError(IOException.class, e -> { - LOG.error("Unlock failed for technical reasons.", e); - messageText.setText(localization.getString("unlock.errorMessage.unlockFailed")); + }).onError(Exception.class, e -> { + LOG.error("Unlock failed for technical reasons.", e); + messageText.setText(localization.getString("unlock.errorMessage.unlockFailed")); }).andFinally(() -> { if (!savePassword.isSelected()) { passwordField.swipe(); } advancedOptions.setDisable(false); progressIndicator.setVisible(false); - }).run(); + }).runOnce(executor); } /* callback */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index d1f0e0417..3143f8c3d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -8,29 +8,13 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import static java.lang.String.format; - -import java.io.IOException; -import java.util.Optional; - import javax.inject.Inject; - -import org.cryptomator.frontend.webdav.ServerLifecycleException; -import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; -import org.cryptomator.ui.l10n.Localization; -import org.cryptomator.ui.model.Vault; -import org.cryptomator.ui.util.AsyncTaskService; -import org.cryptomator.ui.util.DialogBuilderUtil; -import org.fxmisc.easybind.EasyBind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.util.concurrent.Runnables; +import java.util.Optional; +import java.util.concurrent.ExecutorService; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; -import javafx.beans.binding.ObjectExpression; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; @@ -46,13 +30,19 @@ import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; import javafx.scene.control.ToggleButton; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.layout.VBox; import javafx.stage.PopupWindow.AnchorLocation; import javafx.util.Duration; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; +import org.cryptomator.ui.util.DialogBuilderUtil; +import org.cryptomator.ui.util.Tasks; +import org.fxmisc.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.lang.String.format; public class UnlockedController implements ViewController { @@ -62,9 +52,8 @@ public class UnlockedController implements ViewController { private static final double IO_SAMPLING_INTERVAL = 0.25; private final Localization localization; - private final AsyncTaskService asyncTaskService; + private final ExecutorService executor; private final ObjectProperty vault = new SimpleObjectProperty<>(); - private final ObjectExpression vaultState = ObjectExpression.objectExpression(EasyBind.select(vault).selectObject(Vault::stateProperty)); private Optional listener = Optional.empty(); private Timeline ioAnimation; @@ -83,30 +72,17 @@ public class UnlockedController implements ViewController { @FXML private ContextMenu moreOptionsMenu; - @FXML - private MenuItem mountVaultMenuItem; - - @FXML - private MenuItem unmountVaultMenuItem; - - @FXML - private MenuItem revealVaultMenuItem; - @FXML private VBox root; @Inject - public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) { + public UnlockedController(Localization localization, ExecutorService executor) { this.localization = localization; - this.asyncTaskService = asyncTaskService; + this.executor = executor; } @Override public void initialize() { - mountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.UNLOCKED).not()); // enable when unlocked - unmountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted - revealVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted - EasyBind.subscribe(vault, this::vaultChanged); EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected); } @@ -121,10 +97,6 @@ public class UnlockedController implements ViewController { return; } - if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) { - mountVault(newVault); - } - // (re)start throughput statistics: stopIoSampling(); startIoSampling(); @@ -132,75 +104,22 @@ public class UnlockedController implements ViewController { @FXML private void didClickLockVault(ActionEvent event) { - regularUnmountVault(this::lockVault); + regularLockVault(this::lockVaultSucceeded); } - private void lockVault() { - try { - vault.get().lock(); - } catch (ServerLifecycleException | IOException e) { - LOG.error("Lock failed", e); - } + private void lockVaultSucceeded() { listener.ifPresent(listener -> listener.didLock(this)); } - @FXML - private void didClickMoreOptions(ActionEvent event) { - if (moreOptionsMenu.isShowing()) { - moreOptionsMenu.hide(); - } else { - moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT); - moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0); - } - } - - @FXML - public void didClickMountVault(ActionEvent event) { - mountVault(vault.get()); - } - - private void mountVault(Vault vault) { - asyncTaskService.asyncTaskOf(() -> { - vault.mount(); - }).onSuccess(() -> { - LOG.trace("Mount succeeded."); - messageLabel.setText(null); - if (vault.getVaultSettings().revealAfterMount().get()) { - revealVault(vault); - } - }).onError(CommandFailedException.class, e -> { - LOG.error("Mount failed.", e); - // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas? - messageLabel.setText(localization.getString("unlocked.label.mountFailed")); - }).run(); - } - - @FXML - public void didClickUnmountVault(ActionEvent event) { - regularUnmountVault(Runnables.doNothing()); - } - - private void regularUnmountVault(Runnable onSuccess) { - asyncTaskService.asyncTaskOf(() -> { - vault.get().unmount(); + private void regularLockVault(Runnable onSuccess) { + Tasks.create(() -> { + vault.get().lock(false); }).onSuccess(() -> { LOG.trace("Regular unmount succeeded."); onSuccess.run(); }).onError(Exception.class, e -> { onRegularUnmountVaultFailed(e, onSuccess); - }).run(); - } - - private void forcedUnmountVault(Runnable onSuccess) { - asyncTaskService.asyncTaskOf(() -> { - vault.get().unmountForced(); - }).onSuccess(() -> { - LOG.trace("Forced unmount succeeded."); - onSuccess.run(); - }).onError(Exception.class, e -> { - LOG.error("Forced unmount failed.", e); - messageLabel.setText(localization.getString("unlocked.label.unmountFailed")); - }).run(); + }).runOnce(executor); } private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) { @@ -214,7 +133,7 @@ public class UnlockedController implements ViewController { Optional choice = confirmDialog.showAndWait(); if (ButtonType.YES.equals(choice.get())) { - forcedUnmountVault(onSuccess); + forcedLockVault(onSuccess); } else { LOG.trace("Unmount cancelled.", e); } @@ -224,29 +143,43 @@ public class UnlockedController implements ViewController { } } + private void forcedLockVault(Runnable onSuccess) { + Tasks.create(() -> { + vault.get().lock(true); + }).onSuccess(() -> { + LOG.trace("Forced unmount succeeded."); + onSuccess.run(); + }).onError(Exception.class, e -> { + LOG.error("Forced unmount failed.", e); + messageLabel.setText(localization.getString("unlocked.label.unmountFailed")); + }).runOnce(executor); + } + + @FXML + private void didClickMoreOptions(ActionEvent event) { + if (moreOptionsMenu.isShowing()) { + moreOptionsMenu.hide(); + } else { + moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT); + moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0); + } + } + @FXML private void didClickRevealVault(ActionEvent event) { revealVault(vault.get()); } - private void revealVault(Vault vault) { - asyncTaskService.asyncTaskOf(() -> { + void revealVault(Vault vault) { + Tasks.create(() -> { vault.reveal(); }).onSuccess(() -> { LOG.trace("Reveal succeeded."); messageLabel.setText(null); - }).onError(CommandFailedException.class, e -> { + }).onError(Exception.class, e -> { LOG.error("Reveal failed.", e); messageLabel.setText(localization.getString("unlocked.label.revealFailed")); - }).run(); - } - - @FXML - private void didClickCopyUrl(ActionEvent event) { - ClipboardContent clipboardContent = new ClipboardContent(); - clipboardContent.putUrl(vault.get().getFilesystemRootUrl()); - clipboardContent.putString(vault.get().getFilesystemRootUrl()); - Clipboard.getSystemClipboard().setContent(clipboardContent); + }).runOnce(executor); } // **************************************** diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java index 2d7cb7fa9..1eb0c7307 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java @@ -5,19 +5,10 @@ *******************************************************************************/ package org.cryptomator.ui.controllers; +import javax.inject.Inject; import java.util.Objects; import java.util.Optional; - -import javax.inject.Inject; - -import org.cryptomator.ui.controls.SecPasswordField; -import org.cryptomator.ui.l10n.Localization; -import org.cryptomator.ui.model.UpgradeStrategies; -import org.cryptomator.ui.model.UpgradeStrategy; -import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException; -import org.cryptomator.ui.model.Vault; -import org.cryptomator.ui.util.AsyncTaskService; -import org.fxmisc.easybind.EasyBind; +import java.util.concurrent.ExecutorService; import javafx.beans.binding.BooleanExpression; import javafx.beans.property.ObjectProperty; @@ -30,19 +21,26 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.GridPane; +import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.model.UpgradeStrategies; +import org.cryptomator.ui.model.UpgradeStrategy; +import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException; +import org.cryptomator.ui.model.Vault; +import org.cryptomator.ui.util.Tasks; +import org.fxmisc.easybind.EasyBind; public class UpgradeController implements ViewController { private final ObjectProperty strategy = new SimpleObjectProperty<>(); private final UpgradeStrategies strategies; - private final AsyncTaskService asyncTaskService; + private final ExecutorService executor; private Optional listener = Optional.empty(); private Vault vault; @Inject - public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) { + public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) { this.strategies = strategies; - this.asyncTaskService = asyncTaskService; + this.executor = executor; } @FXML @@ -122,8 +120,8 @@ public class UpgradeController implements ViewController { private void upgrade(UpgradeStrategy instruction) { passwordField.setDisable(true); progressIndicator.setVisible(true); - asyncTaskService // - .asyncTaskOf(() -> { + Tasks // + .create(() -> { if (!instruction.isApplicable(vault)) { throw new IllegalStateException("No ugprade needed for " + vault.getPath()); } @@ -137,7 +135,7 @@ public class UpgradeController implements ViewController { progressIndicator.setVisible(false); passwordField.setDisable(false); passwordField.swipe(); - }).run(); + }).runOnce(executor); } private void showNextUpgrade() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index aed401acc..353576862 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -2,24 +2,27 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ package org.cryptomator.ui.controllers; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Comparator; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -30,20 +33,20 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.control.ButtonType; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.VBox; -import jdk.incubator.http.HttpClient; -import jdk.incubator.http.HttpRequest; -import jdk.incubator.http.HttpResponse; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.l10n.Localization; -import org.cryptomator.ui.util.AsyncTaskService; +import org.cryptomator.ui.util.Tasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog; + @Singleton public class WelcomeController implements ViewController { @@ -54,17 +57,17 @@ public class WelcomeController implements ViewController { private final Localization localization; private final Settings settings; private final Comparator semVerComparator; - private final AsyncTaskService asyncTaskService; + private final ScheduledExecutorService executor; @Inject public WelcomeController(Application app, @Named("applicationVersion") Optional applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator semVerComparator, - AsyncTaskService asyncTaskService) { + ScheduledExecutorService executor) { this.app = app; this.applicationVersion = applicationVersion; this.localization = localization; this.settings = settings; this.semVerComparator = semVerComparator; - this.asyncTaskService = asyncTaskService; + this.executor = executor; } @FXML @@ -86,6 +89,8 @@ public class WelcomeController implements ViewController { public void initialize(URL location, ResourceBundle resources) { if (areUpdatesManagedExternally()) { checkForUpdatesContainer.setVisible(false); + } else if (!settings.askedForUpdateCheck().get()) { + this.askForUpdateCheck(); } else if (settings.checkForUpdates().get()) { this.checkForUpdates(); } @@ -104,34 +109,63 @@ public class WelcomeController implements ViewController { return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false")); } + private void askForUpdateCheck() { + Tasks.create(() -> {}).onSuccess(() -> { + Optional result = buildYesNoDialog( + localization.getString("welcome.askForUpdateCheck.dialog.title"), + localization.getString("welcome.askForUpdateCheck.dialog.header"), + localization.getString("welcome.askForUpdateCheck.dialog.content"), + ButtonType.YES).showAndWait(); + if (result.isPresent()) { + settings.askedForUpdateCheck().set(true); + settings.checkForUpdates().set(result.get().equals(ButtonType.YES)); + } + if (settings.checkForUpdates().get()) { + this.checkForUpdates(); + } + }).scheduleOnce(executor, 1, TimeUnit.SECONDS); + } + private void checkForUpdates() { checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking")); checkForUpdatesIndicator.setVisible(true); - asyncTaskService.asyncTaskOf(() -> { + Tasks.create(() -> { String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .GET() - .uri(new URI("https://api.cryptomator.org/updates/latestVersion.json")) - .header("User-Agent", userAgent) - .timeout(Duration.ofSeconds(5)) - .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8)); - if (response.statusCode() == 200) { + URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.addRequestProperty("User-Agent", userAgent); + conn.connect(); + try { + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + return Optional.empty(); + } + try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + in.transferTo(out); + return Optional.of(out.toByteArray()); + } + } finally { + conn.disconnect(); + } + }).onSuccess(response -> { + response.ifPresent(bytes -> { Gson gson = new GsonBuilder().setLenient().create(); - Map map = gson.fromJson(response.body(), new TypeToken>() { + String json = new String(bytes, StandardCharsets.UTF_8); + Map map = gson.fromJson(json, new TypeToken>() { }.getType()); if (map != null) { this.compareVersions(map); } - } + }); + }).onError(Exception.class, e -> { + LOG.warn("Error checking for updates", e); }).andFinally(() -> { checkForUpdatesStatus.setText(""); checkForUpdatesIndicator.setVisible(false); - }).run(); + }).runOnce(executor); } private void compareVersions(final Map latestVersions) { + assert Platform.isFxApplicationThread(); final String latestVersion; if (SystemUtils.IS_OS_MAC_OSX) { latestVersion = latestVersions.get("mac"); @@ -147,11 +181,9 @@ public class WelcomeController implements ViewController { LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion); if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) { final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion); - Platform.runLater(() -> { - this.updateLink.setText(msg); - this.updateLink.setVisible(true); - this.updateLink.setDisable(false); - }); + this.updateLink.setText(msg); + this.updateLink.setVisible(true); + this.updateLink.setDisable(false); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java index f19d4d961..8f7249a16 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java @@ -68,9 +68,7 @@ public class DirectoryListCell extends DraggableListCell { } switch (state) { case UNLOCKED: - case MOUNTED: - case MOUNTING: - case UNMOUNTING: + case PROCESSING: return "\uf09c"; case LOCKED: default: @@ -84,9 +82,7 @@ public class DirectoryListCell extends DraggableListCell { } switch (state) { case UNLOCKED: - case MOUNTED: - case MOUNTING: - case UNMOUNTING: + case PROCESSING: return UNLOCKED_ICON_COLOR; case LOCKED: default: diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java index ba982c872..78b39fc98 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java @@ -73,33 +73,21 @@ public class AutoUnlocker { } try { vault.unlock(CharBuffer.wrap(storedPw)); - mountSilently(vault); - } catch (IOException | CryptoException e) { + revealSilently(vault); + } catch (IOException | CryptoException | Volume.VolumeException e) { LOG.error("Auto unlock failed.", e); } finally { Arrays.fill(storedPw, ' '); } } - private void mountSilently(Vault unlockedVault) { - if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) { - return; - } - try { - unlockedVault.mount(); - revealSilently(unlockedVault); - } catch (CommandFailedException e) { - LOG.error("Auto unlock succeded, but mounting the drive failed.", e); - } - } - private void revealSilently(Vault mountedVault) { if (!mountedVault.getVaultSettings().revealAfterMount().get()) { return; } try { mountedVault.reveal(); - } catch (CommandFailedException e) { + } catch (Volume.VolumeException e) { LOG.error("Auto unlock succeded, but revealing the drive failed.", e); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java b/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java deleted file mode 100644 index 77d04f845..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java +++ /dev/null @@ -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); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java new file mode 100644 index 000000000..792a62364 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java @@ -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 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(); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java index e1c6907d6..0249a198d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -2,6 +2,7 @@ package org.cryptomator.ui.model; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -11,6 +12,7 @@ import javax.inject.Inject; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.frontend.fuse.mount.CommandFailedException; import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; import org.cryptomator.frontend.fuse.mount.FuseMountFactory; import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException; @@ -18,7 +20,6 @@ import org.cryptomator.frontend.fuse.mount.Mount; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@VaultModule.PerVault public class FuseVolume implements Volume { private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); @@ -30,34 +31,21 @@ public class FuseVolume implements Volume { private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator"; private final VaultSettings vaultSettings; - private final WindowsDriveLetters windowsDriveLetters; private Mount fuseMnt; - private CryptoFileSystem cfs; private Path mountPath; private boolean extraDirCreated; @Inject - public FuseVolume(VaultSettings vaultSettings, WindowsDriveLetters windowsDriveLetters) { + public FuseVolume(VaultSettings vaultSettings) { this.vaultSettings = vaultSettings; - this.windowsDriveLetters = windowsDriveLetters; this.extraDirCreated = false; } @Override - public void prepare(CryptoFileSystem fs) throws IOException, FuseNotSupportedException { - this.cfs = fs; + public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException { String mountPath; - if (SystemUtils.IS_OS_WINDOWS) { - //windows case - if (vaultSettings.winDriveLetter().get() != null) { - // specific drive letter selected - mountPath = vaultSettings.winDriveLetter().get() + ":\\"; - } else { - // auto assign drive letter - mountPath = windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\"; - } - } else if (vaultSettings.usesIndividualMountPath().get()) { + if (vaultSettings.usesIndividualMountPath().get()) { //specific path given mountPath = vaultSettings.individualMountPath().get(); } else { @@ -66,57 +54,60 @@ public class FuseVolume implements Volume { extraDirCreated = true; } this.mountPath = Paths.get(mountPath).toAbsolutePath(); + mount(fs.getPath("/")); } private String createDirIfNotExist(String prefix, String dirName) throws IOException { Path p = Paths.get(prefix, dirName + vaultSettings.getId()); - if (Files.isDirectory(p) && !Files.newDirectoryStream(p).iterator().hasNext()) { - throw new DirectoryNotEmptyException("Mount point is not empty."); + if (Files.isDirectory(p)) { + try(DirectoryStream emptyCheck = Files.newDirectoryStream(p)){ + if(emptyCheck.iterator().hasNext()){ + throw new DirectoryNotEmptyException("Mount point is not empty."); + } + else { + LOG.info("Directory already exists and is empty. Using it as mount point."); + return p.toString(); + } + } } else { Files.createDirectory(p); return p.toString(); } } - @Override - public void mount() throws CommandFailedException { + private void mount(Path root) throws VolumeException { try { EnvironmentVariables envVars = EnvironmentVariables.create() .withMountName(vaultSettings.mountName().getValue()) .withMountPath(mountPath) .build(); - this.fuseMnt = FuseMountFactory.getMounter().mount(cfs.getPath("/"), envVars); - } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { - throw new CommandFailedException("Unable to mount Filesystem", e); + this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars); + } catch (CommandFailedException e) { + throw new VolumeException("Unable to mount Filesystem", e); } } @Override - public void reveal() throws CommandFailedException { + public void reveal() throws VolumeException { try { fuseMnt.revealInFileManager(); - } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + } catch (CommandFailedException e) { LOG.info("Revealing the vault in file manger failed: " + e.getMessage()); - throw new CommandFailedException(e); + throw new VolumeException(e); } } @Override - public synchronized void unmount() throws CommandFailedException { + public synchronized void unmount() throws VolumeException { try { fuseMnt.close(); - } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { - throw new CommandFailedException(e); + } catch (CommandFailedException e) { + throw new VolumeException(e); } + cleanup(); } - @Override - public synchronized void unmountForced() throws CommandFailedException { - unmount(); - } - - @Override - public void stop() { + private void cleanup() { if (extraDirCreated) { try { Files.delete(mountPath); @@ -126,19 +117,9 @@ public class FuseVolume implements Volume { } } - @Override - public String getMountUri() { - return ""; - } - @Override public boolean isSupported() { - return FuseMountFactory.isFuseSupported(); - } - - @Override - public boolean supportsForcedUnmount() { - return false; + return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported(); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index f1040a56e..5e2622024 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -19,16 +19,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import javax.inject.Inject; - -import org.cryptomator.common.LazyInitializer; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.VaultSettings; -import org.cryptomator.cryptofs.CryptoFileSystem; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; -import org.cryptomator.cryptofs.CryptoFileSystemProvider; -import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.ui.model.VaultModule.PerVault; +import javax.inject.Provider; import javafx.application.Platform; import javafx.beans.Observable; @@ -39,6 +30,15 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.StringProperty; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.LazyInitializer; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.ui.model.VaultModule.PerVault; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,20 +53,21 @@ public class Vault { private final Settings settings; private final VaultSettings vaultSettings; + private final Provider volumeProvider; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); private Volume volume; public enum State { - LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING + LOCKED, PROCESSING, UNLOCKED } @Inject - Vault(Settings settings, VaultSettings vaultSettings, Volume volume) { + Vault(Settings settings, VaultSettings vaultSettings, Provider volumeProvider) { this.settings = settings; this.vaultSettings = vaultSettings; - this.volume = volume; + this.volumeProvider = volumeProvider; } // ****************************************************************************** @@ -98,51 +99,37 @@ public class Vault { CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase); } - public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException { - CryptoFileSystem fs = getCryptoFileSystem(passphrase); - volume.prepare(fs); - Platform.runLater(() -> { - state.set(State.UNLOCKED); - }); + public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException { + Platform.runLater(() -> state.set(State.PROCESSING)); + try { + CryptoFileSystem fs = getCryptoFileSystem(passphrase); + volume = volumeProvider.get(); + volume.mount(fs); + Platform.runLater(() -> { + state.set(State.UNLOCKED); + }); + } catch (Exception e) { + Platform.runLater(() -> state.set(State.LOCKED)); + throw e; + } } - public synchronized void mount() throws CommandFailedException { + public synchronized void lock(boolean forced) throws Volume.VolumeException { Platform.runLater(() -> { - state.set(State.MOUNTING); - }); - volume.mount(); - Platform.runLater(() -> { - state.set(State.MOUNTED); - }); - } - - public synchronized void unmountForced() throws CommandFailedException { - unmount(true); - } - - public synchronized void unmount() throws CommandFailedException { - unmount(false); - } - - private synchronized void unmount(boolean forced) throws CommandFailedException { - Platform.runLater(() -> { - state.set(State.UNMOUNTING); + state.set(State.PROCESSING); }); if (forced && volume.supportsForcedUnmount()) { volume.unmountForced(); } else { volume.unmount(); } - Platform.runLater(() -> { - state.set(State.UNLOCKED); - }); - } - - public synchronized void lock() throws IOException { - volume.stop(); CryptoFileSystem fs = cryptoFileSystem.getAndSet(null); if (fs != null) { - fs.close(); + try { + fs.close(); + } catch (IOException e) { + LOG.error("Error closing file system.", e); + } } Platform.runLater(() -> { state.set(State.LOCKED); @@ -154,26 +141,21 @@ public class Vault { */ public void prepareForShutdown() { try { - unmount(); - } catch (CommandFailedException e) { + lock(false); + } catch (Volume.VolumeException e) { if (volume.supportsForcedUnmount()) { try { - unmountForced(); - } catch (CommandFailedException e1) { - LOG.warn("Failed to force unmount vault."); + lock(true); + } catch (Volume.VolumeException e1) { + LOG.warn("Failed to force lock vault.", e1); } } else { - LOG.warn("Failed to gracefully unmount vault."); + LOG.warn("Failed to gracefully lock vault.", e); } } - try { - lock(); - } catch (Exception e) { - LOG.warn("Failed to lock vault."); - } } - public void reveal() throws CommandFailedException { + public void reveal() throws Volume.VolumeException { volume.reveal(); } @@ -289,10 +271,6 @@ public class Vault { } } - public String getFilesystemRootUrl() { - return volume.getMountUri(); - } - public String getId() { return vaultSettings.getId(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java index 93c130522..141ba8975 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java @@ -18,10 +18,13 @@ import org.cryptomator.common.settings.VaultSettings; import dagger.Module; import dagger.Provides; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Module public class VaultModule { + private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class); private final VaultSettings vaultSettings; public VaultModule(VaultSettings vaultSettings) { @@ -42,21 +45,18 @@ public class VaultModule { } @Provides - @PerVault - public Volume provideNioAdpater(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume) { - VolumeImpl impl = settings.volumeImpl().get(); - switch (impl) { - case FUSE: - if (fuseVolume.isSupported()) { - return fuseVolume; - } else { - settings.volumeImpl().set(VolumeImpl.WEBDAV); - // fallthrough to WEBDAV - } - case WEBDAV: - return webDavVolume; - default: - throw new IllegalStateException("Unsupported NioAdapter: " + impl); + public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) { + VolumeImpl preferredImpl = settings.preferredVolumeImpl().get(); + if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) { + return dokanyVolume; + } else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) { + return fuseVolume; + } else { + if (VolumeImpl.WEBDAV != preferredImpl) { + LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName()); + } + assert webDavVolume.isSupported(); + return webDavVolume; } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java index e1bdbb6fc..399e765da 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java @@ -9,30 +9,50 @@ import java.io.IOException; */ public interface Volume { - void prepare(CryptoFileSystem fs) throws IOException; + /** + * Checks in constant time whether this volume type is supported on the system running Cryptomator. + * @return true if this volume can be mounted + */ + boolean isSupported(); - void mount() throws CommandFailedException; + /** + * + * @param fs + * @throws IOException + */ + void mount(CryptoFileSystem fs) throws IOException, VolumeException; - default void reveal() throws CommandFailedException { - throw new CommandFailedException("Not implemented."); - } + void reveal() throws VolumeException; - void unmount() throws CommandFailedException; + void unmount() throws VolumeException; - default void unmountForced() throws CommandFailedException { - throw new CommandFailedException("Operation not supported."); - } - - void stop(); - - String getMountUri(); - - default boolean isSupported() { - return false; - } + // optional forced unmounting: default boolean supportsForcedUnmount() { return false; } + default void unmountForced() throws VolumeException { + throw new VolumeException("Operation not supported."); + } + + /** + * Exception thrown when a volume-specific command such as mount/unmount/reveal failed. + */ + class VolumeException extends Exception { + + public VolumeException(String message) { + super(message); + } + + public VolumeException(Throwable cause) { + super(cause); + } + + public VolumeException(String message, Throwable cause) { + super(message, cause); + } + + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java index 037c77a49..6d1138c90 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java @@ -15,7 +15,6 @@ import javax.inject.Provider; import java.net.InetAddress; import java.net.UnknownHostException; -@VaultModule.PerVault public class WebDavVolume implements Volume { private static final String LOCALHOST_ALIAS = "cryptomator-vault"; @@ -36,7 +35,7 @@ public class WebDavVolume implements Volume { } @Override - public void prepare(CryptoFileSystem fs) { + public void mount(CryptoFileSystem fs) throws VolumeException { if (server == null) { server = serverProvider.get(); } @@ -45,10 +44,10 @@ public class WebDavVolume implements Volume { } servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); servlet.start(); + mount(); } - @Override - public void mount() throws CommandFailedException { + private void mount() throws VolumeException { if (servlet == null) { throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); } @@ -61,32 +60,38 @@ public class WebDavVolume implements Volume { this.mount = servlet.mount(mountParams); // might block this thread for a while } catch (Mounter.CommandFailedException e) { e.printStackTrace(); - throw new CommandFailedException(e); + throw new VolumeException(e); } } @Override - public void reveal() throws CommandFailedException { + public void reveal() throws VolumeException { try { mount.reveal(); } catch (Mounter.CommandFailedException e) { e.printStackTrace(); - throw new CommandFailedException(e); + throw new VolumeException(e); } } @Override - public synchronized void unmount() throws CommandFailedException { + public synchronized void unmount() throws VolumeException { try { mount.unmount(); } catch (Mounter.CommandFailedException e) { - throw new CommandFailedException(e); + throw new VolumeException(e); } + cleanup(); } @Override - public synchronized void unmountForced() { - mount.forced(); + public synchronized void unmountForced() throws VolumeException { + try { + mount.forced().orElseThrow(IllegalStateException::new).unmount(); + } catch (Mounter.CommandFailedException e) { + throw new VolumeException(e); + } + cleanup(); } private String getLocalhostAliasOrNull() { @@ -102,28 +107,19 @@ public class WebDavVolume implements Volume { } } - @Override - public void stop() { + private void cleanup() { if (servlet != null) { servlet.stop(); } } - public synchronized String getMountUri() { - return servlet.getServletRootUri().toString() + "/"; - } - - /** - * TODO: what to check wether it is implemented? - * - * @return - */ @Override public boolean isSupported() { return true; } + @Override public boolean supportsForcedUnmount() { return mount != null && mount.forced().isPresent(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java b/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java deleted file mode 100644 index a4aa3f96c..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java +++ /dev/null @@ -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 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 AsyncTaskWithoutSuccessHandler asyncTaskOf(SupplierThrowingException task) { - return new AsyncTaskImpl<>(task); - } - - private class AsyncTaskImpl implements AsyncTaskWithoutSuccessHandler { - - private final SupplierThrowingException task; - - private ConsumerThrowingException successHandler = value -> { - }; - private final List> errorHandlers = new ArrayList<>(); - private RunnableThrowingException finallyHandler = () -> { - }; - - public AsyncTaskImpl(SupplierThrowingException task) { - this.task = task; - } - - @Override - public AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException handler) { - successHandler = handler; - return this; - } - - @Override - public AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException handler) { - return onSuccess(result -> handler.run()); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public AsyncTaskWithoutErrorHandler onError(Class type, ConsumerThrowingException handler) { - errorHandlers.add((ErrorHandler) new ErrorHandler<>(type, handler)); - return this; - } - - @Override - public AsyncTaskWithoutErrorHandler onError(Class 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 errorHandler = errorHandlerFor(e); - Platform.runLater(toRunnableLoggingException(() -> errorHandler.accept(e))); - } finally { - Platform.runLater(toRunnableLoggingException(finallyHandler)); - } - })); - } - - private ErrorHandler 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 implements ConsumerThrowingException { - - public static final ErrorHandler LOGGING_HANDLER = new ErrorHandler(Throwable.class, error -> { - LOG.error("Uncaught exception", error); - }); - - private final Class type; - private final ConsumerThrowingException delegate; - - public ErrorHandler(Class type, ConsumerThrowingException 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 extends AsyncTaskWithoutErrorHandler { - - /** - * @param handler Tasks to be invoked on the JavaFX application thread. - * @return The async task - */ - AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException 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 - */ - AsyncTaskWithoutErrorHandler onError(Class type, ConsumerThrowingException handler); - - /** - * @param type Exception type to catch - * @param handler Tasks to be invoked on the JavaFX application thread. - * @return The async task - */ - AsyncTaskWithoutErrorHandler onError(Class 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(); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java index 056ba8f97..3f9ccd063 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java @@ -28,6 +28,8 @@ import javafx.scene.paint.Color; @Singleton public class PasswordStrengthUtil { + private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length + private final Zxcvbn zxcvbn; private final List sanitizedInputs; private final Localization localization; @@ -43,10 +45,9 @@ public class PasswordStrengthUtil { public int computeRate(String password) { if (Strings.isNullOrEmpty(password)) { return -1; - } else if (password.length() > 100) { - return 4; // assume this is strong. zxcvbn memory and runtime depends vastly on the password length } else { - return zxcvbn.measure(password, sanitizedInputs).getScore(); + int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length()); + return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore(); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/Tasks.java b/main/ui/src/main/java/org/cryptomator/ui/util/Tasks.java new file mode 100644 index 000000000..e3fd77861 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/Tasks.java @@ -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 TaskBuilder create(Callable callable) { + return new TaskBuilder<>(callable); + } + + public static TaskBuilder create(VoidCallable callable) { + return create(() -> { + callable.call(); + return null; + }); + } + + public static class TaskBuilder { + + private final Callable callable; + private Consumer successHandler = x -> { + }; + private List> errorHandlers = new ArrayList<>(); + private Runnable finallyHandler = () -> {}; + + TaskBuilder(Callable callable) { + this.callable = callable; + } + + public TaskBuilder onSuccess(Runnable successHandler) { + this.successHandler = x -> { + successHandler.run(); + }; + return this; + } + + public TaskBuilder onSuccess(Consumer successHandler) { + this.successHandler = successHandler; + return this; + } + + public TaskBuilder onError(Class type, Consumer exceptionHandler) { + errorHandlers.add(new ErrorHandler<>(type, exceptionHandler)); + return this; + } + + public TaskBuilder andFinally(Runnable finallyHandler) { + this.finallyHandler = finallyHandler; + return this; + } + + private Task build() { + return new TaskImpl<>(callable, successHandler, errorHandlers, finallyHandler); + } + + public Task runOnce(ExecutorService executorService) { + Task task = build(); + executorService.submit(task); + return task; + } + + public Task scheduleOnce(ScheduledExecutorService executorService, long delay, TimeUnit unit) { + Task task = build(); + executorService.schedule(task, delay, unit); + return task; + } + + public ScheduledService schedulePeriodically(ExecutorService executorService, Duration initialDelay, Duration period) { + ScheduledService service = new RestartingService<>(this::build); + service.setExecutor(executorService); + service.setDelay(initialDelay); + service.setPeriod(period); + Platform.runLater(service::start); + return service; + } + + } + + private static class ErrorHandler { + + private final Class type; + private final Consumer errorHandler; + + public ErrorHandler(Class type, Consumer 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 extends Task { + + private final Callable callable; + private final Consumer successHandler; + private List> errorHandlers; + private final Runnable finallyHandler; + + TaskImpl(Callable callable, Consumer successHandler, List> 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 extends ScheduledService { + + private final Supplier> taskFactory; + + RestartingService(Supplier> taskFactory) { + this.taskFactory = taskFactory; + } + + @Override + protected Task createTask() { + return taskFactory.get(); + } + + } + + public interface VoidCallable { + + void call() throws Exception; + + } + +} + diff --git a/main/ui/src/main/resources/fxml/unlock.fxml b/main/ui/src/main/resources/fxml/unlock.fxml index 3442ae73e..3e7d88940 100644 --- a/main/ui/src/main/resources/fxml/unlock.fxml +++ b/main/ui/src/main/resources/fxml/unlock.fxml @@ -75,28 +75,25 @@ - + - +