浏览代码

Merge branch 'release/1.4.0-beta3'

Sebastian Stenzel 6 年之前
父节点
当前提交
29038a679f
共有 49 个文件被更改,包括 553 次插入592 次删除
  1. 25 53
      .travis.yml
  2. 0 57
      34C80F11.gpg
  3. 1 1
      main/ant-kit/pom.xml
  4. 1 1
      main/commons/pom.xml
  5. 1 1
      main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java
  6. 1 1
      main/keychain/pom.xml
  7. 1 1
      main/launcher/pom.xml
  8. 6 24
      main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java
  9. 11 15
      main/pom.xml
  10. 1 1
      main/uber-jar/pom.xml
  11. 1 1
      main/ui/pom.xml
  12. 16 1
      main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
  13. 27 30
      main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
  14. 22 50
      main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
  15. 18 12
      main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java
  16. 7 4
      main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java
  17. 5 0
      main/ui/src/main/java/org/cryptomator/ui/model/InvalidSettingsException.java
  18. 7 9
      main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java
  19. 7 5
      main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
  20. 18 1
      main/ui/src/main/java/org/cryptomator/ui/model/Volume.java
  21. 6 1
      main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java
  22. 4 0
      main/ui/src/main/java/org/cryptomator/ui/util/DialogBuilderUtil.java
  23. 8 15
      main/ui/src/main/resources/fxml/settings.fxml
  24. 2 6
      main/ui/src/main/resources/fxml/unlock.fxml
  25. 9 7
      main/ui/src/main/resources/localization/ar.txt
  26. 21 19
      main/ui/src/main/resources/localization/bg.txt
  27. 16 14
      main/ui/src/main/resources/localization/cs.txt
  28. 9 7
      main/ui/src/main/resources/localization/da.txt
  29. 12 10
      main/ui/src/main/resources/localization/de.txt
  30. 9 2
      main/ui/src/main/resources/localization/en.txt
  31. 18 16
      main/ui/src/main/resources/localization/es.txt
  32. 17 15
      main/ui/src/main/resources/localization/fr.txt
  33. 27 25
      main/ui/src/main/resources/localization/hu.txt
  34. 19 17
      main/ui/src/main/resources/localization/it.txt
  35. 9 7
      main/ui/src/main/resources/localization/ja.txt
  36. 7 5
      main/ui/src/main/resources/localization/ko.txt
  37. 9 7
      main/ui/src/main/resources/localization/lv.txt
  38. 17 15
      main/ui/src/main/resources/localization/nl.txt
  39. 9 7
      main/ui/src/main/resources/localization/pl.txt
  40. 7 5
      main/ui/src/main/resources/localization/pt.txt
  41. 15 13
      main/ui/src/main/resources/localization/pt_BR.txt
  42. 19 17
      main/ui/src/main/resources/localization/ru.txt
  43. 9 7
      main/ui/src/main/resources/localization/sk.txt
  44. 9 7
      main/ui/src/main/resources/localization/th.txt
  45. 9 7
      main/ui/src/main/resources/localization/tr.txt
  46. 9 7
      main/ui/src/main/resources/localization/uk.txt
  47. 19 17
      main/ui/src/main/resources/localization/zh.txt
  48. 37 35
      main/ui/src/main/resources/localization/zh_HK.txt
  49. 16 14
      main/ui/src/main/resources/localization/zh_TW.txt

+ 25 - 53
.travis.yml

@@ -2,70 +2,49 @@ language: java
 sudo: false
 jdk:
 - oraclejdk10
-branches:
-  except:
-  - continuous # To avoid infinite loops, as this tag is created by this Travis config
 cache:
   directories:
   - $HOME/.m2
 env:
   global:
-    - secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" # COVERITY_SCAN_TOKEN
     - 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:
     - haveged
-  coverity_scan:
-    project:
-      name: "cryptomator/cryptomator"
-    notification_email: sebastian.stenzel@cryptomator.org
-    build_command: "mvn -fmain/pom.xml clean test -DskipTests"
-    branch_pattern: release.*
 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 verify -Pcoverage
+after_success:
+- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.2/codacy-coverage-reporter-4.0.2-assembly.jar
+- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
+- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
+- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
+- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
+- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
 before_deploy:
-- |
-  if [[ $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'`
-    echo "Existing continuous release: ${CONTINUOUS_RELEASE_URL}"
-    if [[ $CONTINUOUS_RELEASE_URL == http* ]]; then
-      curl -u cryptobot:$GITHUB_API_KEY -X DELETE $CONTINUOUS_RELEASE_URL
-    fi
-  fi
 - |
   if [[ -n "$TRAVIS_TAG" ]]; then
     mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
   elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
     mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
-    git tag -f continuous
-    git remote add gh https://cryptobot:${GITHUB_API_KEY}@github.com/cryptomator/cryptomator.git
-    git push -f gh continuous
-    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
-  api-key: $GITHUB_API_KEY
-  tag_name: continuous
-  overwrite: true
-  file_glob: true
-  file:
-  - "main/uber-jar/target/Cryptomator-*.jar"
-  - "main/ant-kit/target/antkit.zip"
-  - "main/ant-kit/target/antkit.zip.asc"
+- provider: script # SNAPSHOTS
   skip_cleanup: true
-  name: Cryptomator continuous build
-  body: Automatically built on $(date +'%F %T %Z').
+  script: >-
+    curl -T main/ant-kit/target/antkit.zip
+    -u cryptobot:${BINTRAY_API_KEY}
+    -H "X-Bintray-Package:ant-kit"
+    -H "X-Bintray-Version:continuous"
+    -H "X-Bintray-Override:1"
+    -H "X-Bintray-Publish:1"
+    https://api.bintray.com/content/cryptomator/cryptomator/antkit-continuous.zip
   on:
     repo: cryptomator/cryptomator
     branch: develop
@@ -75,27 +54,20 @@ deploy:
   api_key: $GITHUB_API_KEY
   file:
   - "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
-  - "main/ant-kit/target/antkit.zip"
-  - "main/ant-kit/target/antkit.zip.asc"
   skip_cleanup: true
   on:
     repo: cryptomator/cryptomator
     tags: true
 - provider: script
-  script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-win/versions"
-  on:
-    repo: cryptomator/cryptomator
-    tags: true
-- provider: script
-  script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-osx/versions"
+  skip_cleanup: true
+  script: >-
+    curl -T main/ant-kit/target/antkit.zip
+    -u cryptobot:${BINTRAY_API_KEY}
+    -H "X-Bintray-Package:ant-kit"
+    -H "X-Bintray-Version:${TRAVIS_TAG}"
+    -H "X-Bintray-Override:1"
+    -H "X-Bintray-Publish:1"
+    https://api.bintray.com/content/cryptomator/cryptomator/antkit-${TRAVIS_TAG}.zip
   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

+ 0 - 57
34C80F11.gpg

@@ -1,57 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-Version: GnuPG v1
-
-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
-=BRSB
------END PGP PRIVATE KEY BLOCK-----

+ 1 - 1
main/ant-kit/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>org.cryptomator</groupId>
 		<artifactId>main</artifactId>
-		<version>1.4.0-beta2</version>
+		<version>1.4.0-beta3</version>
 	</parent>
 	<artifactId>ant-kit</artifactId>
 	<packaging>pom</packaging>

+ 1 - 1
main/commons/pom.xml

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

+ 1 - 1
main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java

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

+ 1 - 1
main/keychain/pom.xml

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

+ 1 - 1
main/launcher/pom.xml

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

+ 6 - 24
main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java

@@ -5,25 +5,23 @@
  *******************************************************************************/
 package org.cryptomator.launcher;
 
+import javafx.application.Application;
+import javafx.stage.Stage;
 import org.cryptomator.ui.controllers.MainController;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.fxml.FXMLLoader;
-import javafx.stage.Stage;
-
 public class MainApplication extends Application {
 
 	private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
 	private Stage primaryStage;
 
 	@Override
-	public void start(Stage primaryStage) throws Exception {
+	public void start(Stage primaryStage) {
 		LOG.info("JavaFX application started.");
 		this.primaryStage = primaryStage;
-		setupFXMLClassLoader();
+		primaryStage.setMinWidth(652.0);
+		primaryStage.setMinHeight(440.0);
 
 		LauncherModule launcherModule = new LauncherModule(this, primaryStage);
 		LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
@@ -34,30 +32,14 @@ public class MainApplication extends Application {
 
 		MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
 		mainCtrl.initStage(primaryStage);
-
 		primaryStage.show();
 	}
 
 	@Override
-	public void stop() throws Exception {
+	public void stop() {
 		assert primaryStage != null;
 		primaryStage.hide();
 		LOG.info("JavaFX application stopped.");
 	}
 
-	// fix discussed in https://github.com/cryptomator/cryptomator/pull/29
-	private void setupFXMLClassLoader() {
-		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-		FXMLLoader.setDefaultClassLoader(contextClassLoader);
-		Platform.runLater(() -> {
-			/*
-			 * This fixes a bug on OSX where the magic file open handler leads to no context class loader being set in the AppKit (event)
-			 * thread if the application is not started opening a file.
-			 */
-			if (Thread.currentThread().getContextClassLoader() == null) {
-				Thread.currentThread().setContextClassLoader(contextClassLoader);
-			}
-		});
-	}
-
 }

+ 11 - 15
main/pom.xml

@@ -3,7 +3,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>org.cryptomator</groupId>
 	<artifactId>main</artifactId>
-	<version>1.4.0-beta2</version>
+	<version>1.4.0-beta3</version>
 	<packaging>pom</packaging>
 	<name>Cryptomator</name>
 
@@ -24,20 +24,20 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
 		<!-- dependency versions -->
-		<cryptomator.cryptolib.version>1.2.0</cryptomator.cryptolib.version>
-		<cryptomator.cryptofs.version>1.5.2</cryptomator.cryptofs.version>
+		<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
+		<cryptomator.cryptofs.version>1.6.0</cryptomator.cryptofs.version>
 		<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
-		<cryptomator.fuse.version>0.1.5</cryptomator.fuse.version>
-		<cryptomator.dokany.version>0.1.2</cryptomator.dokany.version>
-		<cryptomator.webdav.version>1.0.4</cryptomator.webdav.version>
+		<cryptomator.fuse.version>1.0.0</cryptomator.fuse.version>
+		<cryptomator.dokany.version>1.0.0</cryptomator.dokany.version>
+		<cryptomator.webdav.version>1.0.5</cryptomator.webdav.version>
 
 		<commons-io.version>2.5</commons-io.version>
 		<commons-lang3.version>3.6</commons-lang3.version>
 
 		<easybind.version>1.0.3</easybind.version>
 
-		<guava.version>25.1-jre</guava.version>
-		<dagger.version>2.16</dagger.version>
+		<guava.version>26.0-jre</guava.version>
+		<dagger.version>2.17</dagger.version>
 		<gson.version>2.8.5</gson.version>
 
 		<slf4j.version>1.7.25</slf4j.version>
@@ -45,7 +45,7 @@
 
 		<junit.version>4.12</junit.version>
 		<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
-		<mockito.version>2.19.0</mockito.version>
+		<mockito.version>2.23.0</mockito.version>
 		<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
 	</properties>
 
@@ -288,7 +288,7 @@
 				<plugin>
 					<groupId>org.jacoco</groupId>
 					<artifactId>jacoco-maven-plugin</artifactId>
-					<version>0.8.1</version>
+					<version>0.8.2</version>
 					<executions>
 						<execution>
 							<id>prepare-agent</id>
@@ -315,15 +315,11 @@
 		<plugins>
 			<plugin>
 				<artifactId>maven-compiler-plugin</artifactId>
-				<version>3.7.0</version>
+				<version>3.8.0</version>
 				<configuration>
 					<source>10</source>
 					<target>10</target>
 					<release>10</release>
-					<compilerArgs>
-						<arg>--add-modules</arg>
-						<arg>jdk.incubator.httpclient</arg>
-					</compilerArgs>
 					<annotationProcessorPaths>
 						<path>
 							<groupId>com.google.dagger</groupId>

+ 1 - 1
main/uber-jar/pom.xml

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

+ 1 - 1
main/ui/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>org.cryptomator</groupId>
 		<artifactId>main</artifactId>
-		<version>1.4.0-beta2</version>
+		<version>1.4.0-beta3</version>
 	</parent>
 	<artifactId>ui</artifactId>
 	<name>Cryptomator GUI</name>

+ 16 - 1
main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java

@@ -217,7 +217,22 @@ public class MainController implements ViewController {
 
 	private void gracefulShutdown() {
 		vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
-		Platform.runLater(Platform::exit);
+		if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
+			ButtonType tryAgainButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.tryAgain"));
+			ButtonType forceShutdownButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.forceShutdown"));
+			Alert gracefulShutdownDialog = DialogBuilderUtil.buildGracefulShutdownDialog(
+					localization.getString("main.gracefulShutdown.dialog.title"), localization.getString("main.gracefulShutdown.dialog.header"), localization.getString("main.gracefulShutdown.dialog.content"),
+					forceShutdownButtonType, forceShutdownButtonType, tryAgainButtonType);
+
+			Optional<ButtonType> choice = gracefulShutdownDialog.showAndWait();
+			choice.ifPresent(btnType -> {
+				if (tryAgainButtonType.equals(btnType)) {
+					gracefulShutdown();
+				} else if (forceShutdownButtonType.equals(btnType)) {
+					Platform.runLater(Platform::exit);
+				}
+			});
+		}
 	}
 
 	private void loadFont(String resourcePath) {

+ 27 - 30
main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java

@@ -8,17 +8,13 @@
  ******************************************************************************/
 package org.cryptomator.ui.controllers;
 
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
+import javafx.beans.Observable;
 import javafx.beans.binding.Bindings;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
+import javafx.scene.Group;
 import javafx.scene.Parent;
 import javafx.scene.control.Button;
 import javafx.scene.control.CheckBox;
@@ -26,13 +22,18 @@ import javafx.scene.control.ChoiceBox;
 import javafx.scene.control.Label;
 import javafx.scene.control.TextField;
 import javafx.scene.input.KeyEvent;
-import javafx.scene.layout.GridPane;
 import javafx.scene.layout.VBox;
 import javafx.util.StringConverter;
 import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.settings.VolumeImpl;
 import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VolumeImpl;
 import org.cryptomator.ui.l10n.Localization;
+import org.cryptomator.ui.model.Volume;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.Optional;
 
 @Singleton
 public class SettingsController implements ViewController {
@@ -48,16 +49,13 @@ public class SettingsController implements ViewController {
 		this.localization = localization;
 		this.settings = settings;
 		this.applicationVersion = applicationVersion;
+		this.webdavSettings = new Group();
 	}
 
 	@FXML
 	private CheckBox checkForUpdatesCheckbox;
 
-	@FXML
-	private GridPane webdavVolume;
-
-	@FXML
-	private GridPane fuseVolume;
+	private Group webdavSettings;
 
 	@FXML
 	private Label portFieldLabel;
@@ -93,18 +91,21 @@ public class SettingsController implements ViewController {
 		checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
 
 		//NIOADAPTER
-		volume.getItems().addAll(getSupportedAdapters());
+		volume.getItems().addAll(Volume.getCurrentSupportedAdapters());
 		volume.setValue(settings.preferredVolumeImpl().get());
 		volume.setConverter(new NioAdapterImplStringConverter());
+		volume.valueProperty().addListener(this::setVisibilityGvfsElements);
 
 		//WEBDAV
-		webdavVolume.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.WEBDAV));
-		webdavVolume.managedProperty().bind(webdavVolume.visibleProperty());
-		prefGvfsScheme.managedProperty().bind(webdavVolume.visibleProperty());
-		prefGvfsSchemeLabel.managedProperty().bind(webdavVolume.visibleProperty());
-		portFieldLabel.managedProperty().bind(webdavVolume.visibleProperty());
-		changePortButton.managedProperty().bind(webdavVolume.visibleProperty());
-		portField.managedProperty().bind(webdavVolume.visibleProperty());
+		webdavSettings.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.WEBDAV));
+		webdavSettings.managedProperty().bind(webdavSettings.visibleProperty());
+		prefGvfsScheme.managedProperty().bind(webdavSettings.visibleProperty());
+		prefGvfsSchemeLabel.managedProperty().bind(webdavSettings.visibleProperty());
+		portFieldLabel.managedProperty().bind(webdavSettings.visibleProperty());
+		portFieldLabel.visibleProperty().bind(webdavSettings.visibleProperty());
+		changePortButton.managedProperty().bind(webdavSettings.visibleProperty());
+		portField.managedProperty().bind(webdavSettings.visibleProperty());
+		portField.visibleProperty().bind(webdavSettings.visibleProperty());
 		portField.setText(String.valueOf(settings.port().intValue()));
 		portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
 		changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
@@ -115,10 +116,6 @@ public class SettingsController implements ViewController {
 		prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
 		prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
 
-		//FUSE
-		fuseVolume.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.FUSE));
-		fuseVolume.managedProperty().bind(fuseVolume.visibleProperty());
-
 		debugModeCheckbox.setSelected(settings.debugMode().get());
 
 		settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
@@ -127,11 +124,6 @@ public class SettingsController implements ViewController {
 		settings.debugMode().bind(debugModeCheckbox.selectedProperty());
 	}
 
-	private VolumeImpl[] getSupportedAdapters() {
-		// TODO: filter depending on supported drivers
-		return VolumeImpl.values();
-	}
-
 	@Override
 	public Parent getRoot() {
 		return root;
@@ -167,6 +159,11 @@ public class SettingsController implements ViewController {
 		}
 	}
 
+	private void setVisibilityGvfsElements(Observable obs, Object oldValue, Object newValue) {
+		prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
+		prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
+	}
+
 	private boolean areUpdatesManagedExternally() {
 		return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
 	}

+ 22 - 50
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -9,10 +9,6 @@
 package org.cryptomator.ui.controllers;
 
 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;
@@ -22,7 +18,6 @@ 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;
@@ -39,7 +34,6 @@ 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;
@@ -51,6 +45,7 @@ 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.model.InvalidSettingsException;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.l10n.Localization;
 import org.cryptomator.ui.model.Vault;
@@ -122,18 +117,12 @@ public class UnlockController implements ViewController {
 	@FXML
 	private CheckBox useOwnMountPath;
 
-	@FXML
-	private HBox mountPathBox;
-
 	@FXML
 	private Label mountPathLabel;
 
 	@FXML
 	private TextField mountPath;
 
-	@FXML
-	private Button changeMountPathButton;
-
 	@FXML
 	private ProgressIndicator progressIndicator;
 
@@ -161,13 +150,10 @@ public class UnlockController implements ViewController {
 		savePassword.setDisable(!keychainAccess.isPresent());
 		unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
 
-		mountPathLabel.setVisible(false);
-		mountPathBox.visibleProperty().bind(mountPathLabel.visibleProperty());
-		mountPathBox.managedProperty().bind(mountPathLabel.managedProperty());
-		mountPath.visibleProperty().bind(mountPathLabel.visibleProperty());
-		mountPath.managedProperty().bind(mountPathLabel.managedProperty());
-		changeMountPathButton.visibleProperty().bind(mountPathLabel.visibleProperty());
-		changeMountPathButton.managedProperty().bind(mountPathLabel.managedProperty());
+		mountPathLabel.visibleProperty().bind(useOwnMountPath.selectedProperty());
+		mountPath.visibleProperty().bind(useOwnMountPath.selectedProperty());
+		mountPath.managedProperty().bind(useOwnMountPath.selectedProperty());
+		mountPath.textProperty().addListener(this::mountPathDidChange);
 
 		if (SystemUtils.IS_OS_WINDOWS) {
 			winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
@@ -175,7 +161,7 @@ public class UnlockController implements ViewController {
 			useOwnMountPath.setManaged(false);
 			mountPathLabel.setManaged(false);
 			//dirty cheat
-			mountPathBox.setMouseTransparent(true);
+			mountPath.setMouseTransparent(true);
 		} else {
 			winDriveLetterLabel.setVisible(false);
 			winDriveLetterLabel.setManaged(false);
@@ -187,10 +173,9 @@ public class UnlockController implements ViewController {
 				mountPathLabel.setManaged(false);
 			}
 		}
-		changeMountPathButton.disableProperty().bind(Bindings.createBooleanBinding(this::isDirVaild, mountPath.textProperty()).not());
-
 	}
 
+
 	@Override
 	public Parent getRoot() {
 		return root;
@@ -252,11 +237,8 @@ public class UnlockController implements ViewController {
 		vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
 		vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
 
-		changeMountPathButton.visibleProperty().bind(
-				vaultSettings.individualMountPath().isNotEqualTo(mountPath.textProperty())
-		);
+
 		mountPath.textProperty().setValue(vaultSettings.individualMountPath().getValueSafe());
-		mountPathLabel.visibleProperty().bind(useOwnMountPath.selectedProperty());
 
 	}
 
@@ -284,28 +266,6 @@ public class UnlockController implements ViewController {
 		}
 	}
 
-	@FXML
-	private void didClickchangeMountPathButton(ActionEvent event) {
-		assert isDirVaild();
-		vault.setMountPath(mountPath.getText());
-	}
-
-	private boolean isDirVaild() {
-		try {
-			if (!mountPath.textProperty().isEmpty().get()) {
-				Path p = Paths.get(mountPath.textProperty().get());
-				return Files.isDirectory(p) && Files.isReadable(p) && Files.isWritable(p) && Files.isExecutable(p);
-			} else {
-				return false;
-			}
-
-		} catch (InvalidPathException e) {
-			LOG.info("Invalid path");
-			return false;
-		}
-	}
-
-
 	private void filterAlphanumericKeyEvents(KeyEvent t) {
 		if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) {
 			t.consume();
@@ -321,6 +281,10 @@ public class UnlockController implements ViewController {
 		}
 	}
 
+	private void mountPathDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
+		vault.setIndividualMountPath(newValue);
+	}
+
 	/**
 	 * Converts 'C' to "C:" to translate between model and GUI.
 	 */
@@ -420,6 +384,7 @@ public class UnlockController implements ViewController {
 	@FXML
 	private void didClickUnlockButton(ActionEvent event) {
 		advancedOptions.setDisable(true);
+		advancedOptions.setVisible(false);
 		progressIndicator.setVisible(true);
 
 		CharSequence password = passwordField.getCharacters();
@@ -432,6 +397,10 @@ public class UnlockController implements ViewController {
 			messageText.setText(null);
 			downloadsPageLink.setVisible(false);
 			listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
+		}).onError(InvalidSettingsException.class, e -> {
+			messageText.setText(localization.getString("unlock.errorMessage.invalidMountPath"));
+			advancedOptions.setVisible(true);
+			mountPath.setStyle("-fx-border-color: red;");
 		}).onError(InvalidPassphraseException.class, e -> {
 			messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
 			passwordField.selectAll();
@@ -451,14 +420,17 @@ public class UnlockController implements ViewController {
 			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"));
+			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);
+			if (advancedOptions.isVisible()) { //dirty programming, but otherwise the focus is wrong
+				mountPath.requestFocus();
+			}
 		}).runOnce(executor);
 	}
 

+ 18 - 12
main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java

@@ -1,13 +1,16 @@
 package org.cryptomator.ui.model;
 
 import javax.inject.Inject;
-import java.util.Iterator;
+import java.nio.file.Paths;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
+import com.google.common.collect.Sets;
 import org.cryptomator.common.settings.VaultSettings;
 import org.cryptomator.cryptofs.CryptoFileSystem;
 import org.cryptomator.frontend.dokany.Mount;
 import org.cryptomator.frontend.dokany.MountFactory;
+import org.cryptomator.frontend.dokany.MountFailedException;
 
 public class DokanyVolume implements Volume {
 
@@ -28,7 +31,7 @@ public class DokanyVolume implements Volume {
 
 	@Override
 	public boolean isSupported() {
-		return MountFactory.isApplicable();
+		return DokanyVolume.isSupportedStatic();
 	}
 
 	//TODO: Drive letter 'A' as mount point is invalid in dokany. maybe we should do already here something against it
@@ -39,22 +42,21 @@ public class DokanyVolume implements Volume {
 			driveLetter = vaultSettings.winDriveLetter().get().charAt(0);
 		} else {
 			//auto assign drive letter
-			//TODO: can we assume the we have at least one free drive letter?
-
-			//this is a temporary fix for 'A' being an invalid drive letter
-			if(!windowsDriveLetters.getAvailableDriveLetters().isEmpty()){
-				Iterator<Character> winDriveLetterIt = windowsDriveLetters.getAvailableDriveLetters().iterator();
-				do{
-					driveLetter = winDriveLetterIt.next();
-				}while (winDriveLetterIt.hasNext() && driveLetter == 65);
-//			if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
+			if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
+				//this is a temporary fix for 'A' being an invalid drive letter
+				Set<Character> availableLettersWithoutA = Sets.difference(windowsDriveLetters.getAvailableDriveLetters(), Set.of('A'));
+				driveLetter = availableLettersWithoutA.iterator().next();
 //				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);
+		try {
+			this.mount = mountFactory.mount(fs.getPath("/"), Paths.get(driveLetter + ":\\") , mountName, FS_TYPE_NAME);
+		} catch (MountFailedException e) {
+			throw new VolumeException("Unable to mount Filesystem", e);
+		}
 	}
 
 	@Override
@@ -69,4 +71,8 @@ public class DokanyVolume implements Volume {
 	public void unmount() {
 		mount.close();
 	}
+
+	public static boolean isSupportedStatic() {
+		return MountFactory.isApplicable();
+	}
 }

+ 7 - 4
main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java

@@ -60,11 +60,10 @@ public class FuseVolume implements Volume {
 	private String createDirIfNotExist(String prefix, String dirName) throws IOException {
 		Path p = Paths.get(prefix, dirName + vaultSettings.getId());
 		if (Files.isDirectory(p)) {
-			try(DirectoryStream<Path> emptyCheck = Files.newDirectoryStream(p)){
-				if(emptyCheck.iterator().hasNext()){
+			try (DirectoryStream<Path> emptyCheck = Files.newDirectoryStream(p)) {
+				if (emptyCheck.iterator().hasNext()) {
 					throw new DirectoryNotEmptyException("Mount point is not empty.");
-				}
-				else {
+				} else {
 					LOG.info("Directory already exists and is empty. Using it as mount point.");
 					return p.toString();
 				}
@@ -119,6 +118,10 @@ public class FuseVolume implements Volume {
 
 	@Override
 	public boolean isSupported() {
+		return FuseVolume.isSupportedStatic();
+	}
+
+	public static boolean isSupportedStatic() {
 		return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
 	}
 

+ 5 - 0
main/ui/src/main/java/org/cryptomator/ui/model/InvalidSettingsException.java

@@ -0,0 +1,5 @@
+package org.cryptomator.ui.model;
+
+public class InvalidSettingsException extends RuntimeException {
+
+}

+ 7 - 9
main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java

@@ -5,8 +5,8 @@
  *******************************************************************************/
 package org.cryptomator.ui.model;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
+import javax.inject.Inject;
+import javax.inject.Singleton;
 import java.io.IOException;
 import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitResult;
@@ -19,11 +19,7 @@ import java.util.EnumSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.apache.commons.codec.binary.Base32;
-import org.apache.commons.codec.binary.BaseNCodec;
+import com.google.common.io.BaseEncoding;
 import org.apache.commons.lang3.StringUtils;
 import org.cryptomator.cryptolib.Cryptors;
 import org.cryptomator.cryptolib.api.Cryptor;
@@ -32,6 +28,8 @@ import org.cryptomator.ui.l10n.Localization;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 /**
  * Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
  * This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
@@ -50,7 +48,7 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
 	private static final String NEW_FOLDER_PREFIX = "0";
 
 	private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
-	private final BaseNCodec base32 = new Base32();
+	private final BaseEncoding base32 = BaseEncoding.base32();
 
 	@Inject
 	public UpgradeVersion3to4(Localization localization) {
@@ -155,7 +153,7 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
 			String oldLongName = new String(Files.readAllBytes(oldMetadataFile), UTF_8);
 			if (oldLongName.endsWith(OLD_FOLDER_SUFFIX)) {
 				String newLongName = NEW_FOLDER_PREFIX + StringUtils.removeEnd(oldLongName, OLD_FOLDER_SUFFIX);
-				String newCanonicalBase32 = base32.encodeAsString(sha1.digest(newLongName.getBytes(UTF_8)));
+				String newCanonicalBase32 = base32.encode(sha1.digest(newLongName.getBytes(UTF_8)));
 				String newCanonicalName = newCanonicalBase32 + LONG_FILENAME_EXT;
 				Path newMetadataFile = metadataDir.resolve(newCanonicalName.substring(0, 2)).resolve(newCanonicalName.substring(2, 4)).resolve(newCanonicalName);
 				String newName = newCanonicalBase32 + oldNameSuffix + LONG_FILENAME_EXT;

+ 7 - 5
main/ui/src/main/java/org/cryptomator/ui/model/Vault.java

@@ -27,7 +27,6 @@ import javafx.beans.binding.Binding;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyObjectProperty;
 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;
@@ -99,9 +98,12 @@ public class Vault {
 		CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
 	}
 
-	public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
+	public synchronized void unlock(CharSequence passphrase) throws InvalidSettingsException, CryptoException, IOException, Volume.VolumeException {
 		Platform.runLater(() -> state.set(State.PROCESSING));
 		try {
+			if (vaultSettings.usesIndividualMountPath().and(vaultSettings.individualMountPath().isEmpty()).get()) {
+				throw new InvalidSettingsException();
+			}
 			CryptoFileSystem fs = getCryptoFileSystem(passphrase);
 			volume = volumeProvider.get();
 			volume.mount(fs);
@@ -239,11 +241,11 @@ public class Vault {
 		return vaultSettings.mountName().get();
 	}
 
-	public StringProperty getMountPathProperty() {
-		return vaultSettings.individualMountPath();
+	public String getIndividualMountPath() {
+		return vaultSettings.individualMountPath().getValueSafe();
 	}
 
-	public void setMountPath(String mountPath) {
+	public void setIndividualMountPath(String mountPath) {
 		vaultSettings.individualMountPath().set(mountPath);
 	}
 

+ 18 - 1
main/ui/src/main/java/org/cryptomator/ui/model/Volume.java

@@ -1,8 +1,10 @@
 package org.cryptomator.ui.model;
 
+import org.cryptomator.common.settings.VolumeImpl;
 import org.cryptomator.cryptofs.CryptoFileSystem;
 
 import java.io.IOException;
+import java.util.stream.Stream;
 
 /**
  * Takes a Volume and usess it to mount an unlocked vault
@@ -11,12 +13,12 @@ public interface Volume {
 
 	/**
 	 * 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();
 
 	/**
-	 *
 	 * @param fs
 	 * @throws IOException
 	 */
@@ -36,6 +38,21 @@ public interface Volume {
 		throw new VolumeException("Operation not supported.");
 	}
 
+	static VolumeImpl[] getCurrentSupportedAdapters() {
+		return Stream.of(VolumeImpl.values()).filter(impl -> {
+			switch (impl) {
+				case WEBDAV:
+					return WebDavVolume.isSupportedStatic();
+				case DOKANY:
+					return DokanyVolume.isSupportedStatic();
+				case FUSE:
+					return FuseVolume.isSupportedStatic();
+				default:
+					return false;//throw new IllegalStateException("Adapter not implemented.");
+			}
+		}).toArray(VolumeImpl[]::new);
+	}
+
 	/**
 	 * Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
 	 */

+ 6 - 1
main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java

@@ -116,11 +116,16 @@ public class WebDavVolume implements Volume {
 
 	@Override
 	public boolean isSupported() {
-		return true;
+		return WebDavVolume.isSupportedStatic();
 	}
 
 	@Override
 	public boolean supportsForcedUnmount() {
 		return mount != null && mount.forced().isPresent();
 	}
+
+
+	public static boolean isSupportedStatic() {
+		return true;
+	}
 }

+ 4 - 0
main/ui/src/main/java/org/cryptomator/ui/util/DialogBuilderUtil.java

@@ -38,6 +38,10 @@ public class DialogBuilderUtil {
 		return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton, ButtonType.YES, ButtonType.NO);
 	}
 
+	public static Alert buildGracefulShutdownDialog(String title, String header, String content, ButtonType defaultButton, ButtonType... buttons) {
+		return buildDialog(title, header, content, Alert.AlertType.WARNING, defaultButton, buttons);
+	}
+
 	private static Alert buildDialog(String title, String header, String content, Alert.AlertType type, ButtonType defaultButton, ButtonType... buttons) {
 		Text contentText = new Text(content);
 		contentText.setWrappingWidth(360.0);

+ 8 - 15
main/ui/src/main/resources/fxml/settings.fxml

@@ -7,7 +7,6 @@
   Contributors:
       Sebastian Stenzel - initial API and implementation
 -->
-<?import java.net.URL?>
 <?import javafx.scene.layout.GridPane?>
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.layout.ColumnConstraints?>
@@ -45,21 +44,15 @@
 			<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="volume" cacheShape="true" cache="true" />
 
 			<!-- Row 3 Alt 1-->
-			<GridPane fx:id="webdavVolume" vgap="12.0" hgap="12.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="true" cacheShape="true" cache="true">
-				<Label fx:id="portFieldLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.webdav.port.label" cacheShape="true" cache="true" />
-				<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
-					<TextField  fx:id="portField" cacheShape="true" cache="true" promptText="%settings.webdav.port.prompt" />
-					<Button text="%settings.webdav.port.apply" fx:id="changePortButton" onAction="#changePort"/>
-				</HBox>
+			<Label fx:id="portFieldLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.webdav.port.label" cacheShape="true" cache="true" />
+			<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
+				<TextField  fx:id="portField" cacheShape="true" cache="true" promptText="%settings.webdav.port.prompt" />
+				<Button text="%settings.webdav.port.apply" fx:id="changePortButton" onAction="#changePort"/>
+			</HBox>
 
-				<!-- Row 4 -->
-				<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.webdav.prefGvfsScheme.label" cacheShape="true" cache="true" />
-				<ChoiceBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
-			</GridPane>
-
-			<!-- Row 3 Alt 2-->
-			<GridPane fx:id="fuseVolume" vgap="12.0" hgap="12.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
-			</GridPane>
+			<!-- Row 4 -->
+			<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.webdav.prefGvfsScheme.label" cacheShape="true" cache="true" />
+			<ChoiceBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
 
 		</children>
 	</GridPane>

+ 2 - 6
main/ui/src/main/resources/fxml/unlock.fxml

@@ -88,14 +88,10 @@
 			<ChoiceBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
 
 			<!-- Row 3.5 Alt2 -->
-			<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" fx:id="useOwnMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
+			<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useOwnMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
 
 			<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="mountPathLabel" text="%unlock.label.mountPath" cacheShape="true" cache="true" />
-
-			<HBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="mountPathBox" spacing="6.0">
-				<TextField fx:id="mountPath"  cacheShape="true" cache="true" />
-				<Button text="%unlock.label.mountPathButton" fx:id="changeMountPathButton" onAction="#didClickchangeMountPathButton"/>
-			</HBox>
+			<TextField GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="mountPath" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
 
 		</GridPane>
 		

文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/ar.txt


文件差异内容过多而无法显示
+ 21 - 19
main/ui/src/main/resources/localization/bg.txt


文件差异内容过多而无法显示
+ 16 - 14
main/ui/src/main/resources/localization/cs.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/da.txt


文件差异内容过多而无法显示
+ 12 - 10
main/ui/src/main/resources/localization/de.txt


+ 9 - 2
main/ui/src/main/resources/localization/en.txt

@@ -19,6 +19,11 @@ main.directoryList.remove.confirmation.content=The vault will only be removed fr
 main.createVault.nonEmptyDir.title=Creating vault failed
 main.createVault.nonEmptyDir.header=Chosen directory is not empty
 main.createVault.nonEmptyDir.content=The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
+main.gracefulShutdown.dialog.title=Locking vault(s) failed
+main.gracefulShutdown.dialog.header=Vault(s) in use
+main.gracefulShutdown.dialog.content=One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
+main.gracefulShutdown.button.tryAgain=Try again
+main.gracefulShutdown.button.forceShutdown=Force shutdown
 
 # welcome.fxml
 welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
@@ -69,8 +74,8 @@ unlock.label.mountName=Drive Name
 unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
 unlock.label.revealAfterMount=Reveal Drive
 unlock.label.winDriveLetter=Drive Letter
-unlock.label.useOwnMountPath=Use own Mount point
-unlock.label.mountPath=Mount Path
+unlock.label.useOwnMountPath=Use Custom Mount Point
+unlock.label.mountPath=Mount path
 unlock.label.mountPathButton=Apply
 unlock.label.downloadsPageLink=All Cryptomator versions
 unlock.label.advancedHeading=Advanced Options
@@ -82,6 +87,8 @@ unlock.savePassword.delete.confirmation.header=Do you really want to delete the
 unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
 unlock.choicebox.winDriveLetter.auto=Assign automatically
 unlock.errorMessage.wrongPassword=Wrong password
+unlock.errorMessage.wrongPassword=Wrong Password
+unlock.errorMessage.invalidMountPath=Individual mount path is not set.
 unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
 unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
 unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.

文件差异内容过多而无法显示
+ 18 - 16
main/ui/src/main/resources/localization/es.txt


文件差异内容过多而无法显示
+ 17 - 15
main/ui/src/main/resources/localization/fr.txt


文件差异内容过多而无法显示
+ 27 - 25
main/ui/src/main/resources/localization/hu.txt


文件差异内容过多而无法显示
+ 19 - 17
main/ui/src/main/resources/localization/it.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/ja.txt


文件差异内容过多而无法显示
+ 7 - 5
main/ui/src/main/resources/localization/ko.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/lv.txt


文件差异内容过多而无法显示
+ 17 - 15
main/ui/src/main/resources/localization/nl.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/pl.txt


文件差异内容过多而无法显示
+ 7 - 5
main/ui/src/main/resources/localization/pt.txt


文件差异内容过多而无法显示
+ 15 - 13
main/ui/src/main/resources/localization/pt_BR.txt


文件差异内容过多而无法显示
+ 19 - 17
main/ui/src/main/resources/localization/ru.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/sk.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/th.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/tr.txt


文件差异内容过多而无法显示
+ 9 - 7
main/ui/src/main/resources/localization/uk.txt


+ 19 - 17
main/ui/src/main/resources/localization/zh.txt

@@ -45,7 +45,6 @@ changePassword.errorMessage.decryptionFailed = 解密失败
 # unlocked.fxml
 unlocked.button.lock = 锁定资料库
 unlocked.moreOptions.reveal = 打开驱动器
-unlocked.moreOptions.copyUrl = 复制 WebDAV 网址
 unlocked.label.revealFailed = 指令无法执行
 unlocked.label.unmountFailed = 无法弹出驱动器
 unlocked.label.statsEncrypted = 已加密
@@ -87,29 +86,32 @@ upgrade.version3to4.title = 升级资料库到第四版
 upgrade.version4to5.title = 升级资料库到第五版
 upgrade.version4to5.msg = 此资料库需要升级至最新版本,\n已加密的数据将被更新。\n请确保同步完成后再继续操作。
 upgrade.version4to5.err.io = 升级因 I/O 错误失败。请查看日志来获得详细信息。
-unlock.label.mountAfterUnlock = 装载驱动器
 unlock.label.revealAfterMount = 显示驱动器
 unlocked.lock.force.confirmation.title = 无法锁定 %1$s
 unlocked.lock.force.confirmation.header = 要强制锁定吗?
 unlocked.lock.force.confirmation.content = 此错误可能是因为其他应用程序仍在使用此资料库中的文件,或遇到了其他问题。\n\n如强制锁定,正在使用文件的应用程序可能会出错并丢失部分还没有保存的资料。
 unlock.label.unlockAfterStartup = 启动时自动解锁(试验功能)
 unlock.errorMessage.unlockFailed = 无法解锁。请查看日志来获得详细信息。
-unlocked.moreOptions.mount = 装载驱动器
-unlocked.moreOptions.unmount = 弹出驱动器
 upgrade.version5toX.title = 资料库版本升级
 upgrade.version5toX.msg = 此资料库需要升级至最新版本,\n请确保同步完成后再继续操作。
-main.createVault.nonEmptyDir.title = Creating vault failed
-main.createVault.nonEmptyDir.header = Chosen directory is not empty
-main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
-unlock.label.mountPath = Mount Path
-unlock.label.mountPathButton = Apply
-settings.webdav.port.label = WebDAV Port
-settings.webdav.port.prompt = 0 \= Choose automatically
-settings.webdav.port.apply = Apply
-settings.webdav.prefGvfsScheme.label = WebDAV Scheme
-settings.volume.label = Mount-Methode *
+main.createVault.nonEmptyDir.title = 创建资料库失败
+main.createVault.nonEmptyDir.header = 选择的目录不为空
+main.createVault.nonEmptyDir.content = 选择的目录含有文件(可能是隐藏的)。资料库只能创建在空目录
+unlock.label.mountPath = 挂载路径
+unlock.label.mountPathButton = 设定
+settings.webdav.port.label = webDav端口
+settings.webdav.port.prompt = 0\=自动选择WebDav端口
+settings.webdav.port.apply = 设定
+settings.webdav.prefGvfsScheme.label = WebDAV计划
+settings.volume.label = 首选卷类型
 settings.volume.webdav = WebDAV
 settings.volume.fuse = FUSE
-unlock.successLabel.vaultCreated = Vault was successfully created.
-unlock.successLabel.passwordChanged = Password was successfully changed.
-unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
+unlock.successLabel.vaultCreated = 资料库成功创建
+unlock.successLabel.passwordChanged = 密码成功更改
+unlock.successLabel.upgraded = Cryptomator 成功升级
+unlock.label.useOwnMountPath = 无效的挂载点
+welcome.askForUpdateCheck.dialog.title = 检查更新
+welcome.askForUpdateCheck.dialog.header = 启用更新检查?
+welcome.askForUpdateCheck.dialog.content = 启用检查更新,Cryptomator将从Cryptomator服务器获取当前版本,并在有新版本时提示。\n我们建议您启用更新检查,以确保安装了最新版的Cryptomator,并安装了所有安全补丁。如果您未启用更新检查,则需要您自己到https\://cryptomator.org/downloads/ 检查并下载最新版本。\n你可以随时在设置中更改此设置。
+settings.volume.dokany = Dokany
+unlock.errorMessage.invalidMountPath = 未设置单独的挂载路径

文件差异内容过多而无法显示
+ 37 - 35
main/ui/src/main/resources/localization/zh_HK.txt


文件差异内容过多而无法显示
+ 16 - 14
main/ui/src/main/resources/localization/zh_TW.txt