diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 0504eebce..4775a0aa2 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -5,6 +5,7 @@ labels: type:bug
---
+
### Description
@@ -23,7 +26,7 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
* Operating system and version: [Windows/macOS/Linux + Version]
* Cryptomator version: [Shown in the settings]
-* Drive: [Dokany/FUSE/WebDAV]
+* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
### Steps to Reproduce
@@ -58,4 +61,4 @@ Log file location:
- macOS: ~/Library/Logs/Cryptomator
- Linux: ~/.local/share/Cryptomator/logs
--->
\ No newline at end of file
+-->
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c38d20dc5..b847d0b4b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,6 +7,7 @@ jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
@@ -27,28 +28,31 @@ jobs:
run: |
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-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
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
- - name: Assemble Buildkit
- run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
+ - name: Assemble buildkit-linux.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
- name: Upload buildkit-linux.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-linux.zip
+ name: buildkit-linux
path: main/buildkit/target/buildkit-linux.zip
+ - name: Assemble buildkit-mac.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,mac
- name: Upload buildkit-mac.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-mac.zip
+ name: buildkit-mac
path: main/buildkit/target/buildkit-mac.zip
+ - name: Assemble buildkit-win.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,windows
- name: Upload buildkit-win.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-win.zip
+ name: buildkit-win
path: main/buildkit/target/buildkit-win.zip
release:
diff --git a/.github/workflows/triageBugs.yml b/.github/workflows/triageBugs.yml
new file mode 100644
index 000000000..f3354fb11
--- /dev/null
+++ b/.github/workflows/triageBugs.yml
@@ -0,0 +1,25 @@
+name: Bug Report Triage
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ closeTemplateViolation:
+ name: Close bug reports that violate the issue template
+ runs-on: ubuntu-latest
+ steps:
+ - if: |
+ contains(github.event.issue.labels.*.name, 'type:bug')
+ && (
+ !contains(github.event.issue.body, '')
+ || !contains(github.event.issue.body, '### Description')
+ )
+ name: Close Issue
+ uses: peter-evans/close-issue@v1
+ with:
+ comment: |
+ This bug report did ignore our issue template. 😞
+ Auto-closing this issue, since it is most likely not useful.
+
+ _This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3f055e749..900a21ae0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,5 +18,8 @@ pom.xml.versionsBackup
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
+.idea/compiler.xml
+.idea/encodings.xml
+.idea/jarRepositories.xml
.idea/**/libraries/
*.iml
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index d6cb2c866..d361191e8 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -9,14 +9,36 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -30,12 +52,17 @@
+
+
+
+
+
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index e8dfd8106..000000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index c3807468d..000000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 727c7b1bb..000000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Cryptomator_Linux_Dev.xml b/.idea/runConfigurations/Cryptomator_Linux_Dev.xml
new file mode 100644
index 000000000..945c99d68
--- /dev/null
+++ b/.idea/runConfigurations/Cryptomator_Linux_Dev.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Cryptomator_Windows.xml b/.idea/runConfigurations/Cryptomator_Windows.xml
index 05852ad59..c1143a530 100644
--- a/.idea/runConfigurations/Cryptomator_Windows.xml
+++ b/.idea/runConfigurations/Cryptomator_Windows.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/.idea/runConfigurations/Cryptomator_Windows_Dev.xml b/.idea/runConfigurations/Cryptomator_Windows_Dev.xml
new file mode 100644
index 000000000..1cea10200
--- /dev/null
+++ b/.idea/runConfigurations/Cryptomator_Windows_Dev.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Cryptomator_macOS_Dev.xml b/.idea/runConfigurations/Cryptomator_macOS_Dev.xml
new file mode 100644
index 000000000..bda578f65
--- /dev/null
+++ b/.idea/runConfigurations/Cryptomator_macOS_Dev.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 091b6af8c..e07ab285f 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,9 @@ For more information on the security details visit [cryptomator.org](https://doc
```
cd main
-mvn clean install -Prelease
+mvn clean install -Prelease,windows
+# or mvn clean install -Prelease,mac
+# or mvn clean install -Prelease,linux
```
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.
diff --git a/main/buildkit/assembly-linux.xml b/main/buildkit/assembly-linux.xml
index b618c7d15..1e16da9e2 100644
--- a/main/buildkit/assembly-linux.xml
+++ b/main/buildkit/assembly-linux.xml
@@ -1,5 +1,5 @@
-tarballfalse
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
- target/
@@ -43,12 +36,5 @@
libs
-
- target/linux-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/assembly-mac.xml b/main/buildkit/assembly-mac.xml
index 3b1bf9666..4a01f4067 100644
--- a/main/buildkit/assembly-mac.xml
+++ b/main/buildkit/assembly-mac.xml
@@ -1,5 +1,5 @@
-tarballfalse
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
- target/
@@ -43,12 +36,5 @@
libs
-
- target/mac-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/assembly-win.xml b/main/buildkit/assembly-win.xml
index 928994c5f..0297f3ec4 100644
--- a/main/buildkit/assembly-win.xml
+++ b/main/buildkit/assembly-win.xml
@@ -1,5 +1,5 @@
-tarballfalse
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
- target/
@@ -43,12 +36,5 @@
libs
-
- target/win-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml
index c86927d33..4dde902b9 100644
--- a/main/buildkit/pom.xml
+++ b/main/buildkit/pom.xml
@@ -1,5 +1,5 @@
-
+4.0.0org.cryptomator
@@ -24,7 +24,6 @@
org.apache.maven.pluginsmaven-resources-plugin
- 3.1.0copy-resources
@@ -55,8 +54,8 @@
+ org.apache.maven.pluginsmaven-dependency-plugin
- 3.1.1copy-libs
@@ -65,110 +64,153 @@
copy-dependencies
+ runtime${project.build.directory}/libslinux,mac,win
- dbus-java,secret-service,hkdf,java-utils
-
-
-
- copy-linux-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/linux-libs
- org.openjfx
- linux
-
-
-
- copy-linux-secret-service
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/linux-libs
- dbus-java,secret-service,hkdf,java-utils
-
-
-
- copy-mac-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/mac-libs
- org.openjfx
- mac
-
-
-
- copy-win-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/win-libs
- org.openjfx
- win
-
-
-
-
-
-
-
- maven-assembly-plugin
- 3.1.1
-
-
- assemble-linux
- package
-
- single
-
-
-
- assembly-linux.xml
-
- false
- buildkit-linux
-
-
-
- assemble-mac
- package
-
- single
-
-
-
- assembly-mac.xml
-
- false
- buildkit-mac
-
-
-
- assemble-win
- package
-
- single
-
-
-
- assembly-win.xml
-
- false
- buildkit-win
+
+
+
+ linux
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-linux
+ package
+
+ single
+
+
+
+ assembly-linux.xml
+
+ false
+ buildkit-linux
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-linux-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ linux
+
+
+
+
+
+
+
+
+
+ mac
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-mac
+ package
+
+ single
+
+
+
+ assembly-mac.xml
+
+ false
+ buildkit-mac
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-mac-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ mac
+
+
+
+
+
+
+
+
+
+ windows
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-win
+ package
+
+ single
+
+
+
+ assembly-win.xml
+
+ false
+ buildkit-win
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-win-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ win
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/buildkit/src/main/resources/ffi-version.txt b/main/buildkit/src/main/resources/ffi-version.txt
deleted file mode 100644
index 1853c270c..000000000
--- a/main/buildkit/src/main/resources/ffi-version.txt
+++ /dev/null
@@ -1 +0,0 @@
-${cryptomator.jni.version}
\ No newline at end of file
diff --git a/main/buildkit/src/main/resources/launcher-linux.sh b/main/buildkit/src/main/resources/launcher-linux.sh
index 2322870e8..b64d2c1ce 100644
--- a/main/buildkit/src/main/resources/launcher-linux.sh
+++ b/main/buildkit/src/main/resources/launcher-linux.sh
@@ -1,4 +1,5 @@
#!/bin/sh
+cd $(dirname $0)
java \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
diff --git a/main/buildkit/src/main/resources/launcher-mac.sh b/main/buildkit/src/main/resources/launcher-mac.sh
index 65f8ba10d..2c06efab2 100644
--- a/main/buildkit/src/main/resources/launcher-mac.sh
+++ b/main/buildkit/src/main/resources/launcher-mac.sh
@@ -1,4 +1,5 @@
#!/bin/sh
+cd $(dirname $0)
java \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
diff --git a/main/buildkit/src/main/resources/launcher-win.bat b/main/buildkit/src/main/resources/launcher-win.bat
index 505981c9e..d385d7e28 100644
--- a/main/buildkit/src/main/resources/launcher-win.bat
+++ b/main/buildkit/src/main/resources/launcher-win.bat
@@ -4,6 +4,7 @@ java ^
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
+ -Dcryptomator.mountPointsDir="~/Cryptomator" ^
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
-Xss20m ^
-Xmx512m ^
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 54e711559..28a42ec07 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -1,5 +1,5 @@
-
+4.0.0org.cryptomator
@@ -29,7 +29,7 @@
org.cryptomator
- jni
+ integrations-api
@@ -44,7 +44,7 @@
- org.fxmisc.easybind
+ com.tobiasdiezeasybind
diff --git a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
index bac0114da..ed278e1ab 100644
--- a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
+++ b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
@@ -5,24 +5,25 @@
*******************************************************************************/
package org.cryptomator.common;
+import com.tobiasdiez.easybind.EasyBind;
import dagger.Module;
import dagger.Provides;
-import javafx.beans.binding.Binding;
-import javafx.beans.binding.Bindings;
-import javafx.collections.ObservableList;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.frontend.webdav.WebDavServer;
-import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
+import javafx.beans.binding.Binding;
+import javafx.beans.binding.Bindings;
+import javafx.collections.ObservableList;
import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.concurrent.ExecutorService;
@@ -33,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-@Module(subcomponents = {VaultComponent.class})
+@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
public abstract class CommonsModule {
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java
index 890840b7d..d920b1b9f 100644
--- a/main/commons/src/main/java/org/cryptomator/common/Environment.java
+++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java
@@ -40,6 +40,7 @@ public class Environment {
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
+ LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
}
public boolean useCustomLogbackConfig() {
@@ -74,6 +75,10 @@ public class Environment {
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
}
+ public boolean useExperimentalFuse() {
+ return Boolean.getBoolean("fuse.experimental");
+ }
+
private int getInt(String propertyName, int defaultValue) {
String value = System.getProperty(propertyName);
try {
diff --git a/main/commons/src/main/java/org/cryptomator/common/JniModule.java b/main/commons/src/main/java/org/cryptomator/common/JniModule.java
deleted file mode 100644
index 180736710..000000000
--- a/main/commons/src/main/java/org/cryptomator/common/JniModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2019 Skymatic GmbH.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.common;
-
-import dagger.Module;
-import dagger.Provides;
-import org.cryptomator.jni.JniFunctions;
-import org.cryptomator.jni.MacFunctions;
-import org.cryptomator.jni.WinFunctions;
-
-import javax.inject.Singleton;
-import java.util.Optional;
-
-@Module
-public class JniModule {
-
- @Provides
- @Singleton
- Optional provideOptionalMacFunctions() {
- return JniFunctions.macFunctions();
- }
-
- @Provides
- @Singleton
- Optional provideOptionalWinFunctions() {
- return JniFunctions.winFunctions();
- }
-
-}
diff --git a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
index d58d5af36..7bca69457 100644
--- a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
+++ b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
@@ -5,12 +5,12 @@
*******************************************************************************/
package org.cryptomator.common;
+import com.google.common.base.Throwables;
+
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
-import com.google.common.base.Throwables;
-
public final class LazyInitializer {
private LazyInitializer() {
@@ -18,7 +18,7 @@ public final class LazyInitializer {
/**
* Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
- *
+ *
* @param Type of the value
* @param reference A reference to a maybe not yet initialized value.
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
@@ -31,7 +31,7 @@ public final class LazyInitializer {
/**
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
- *
+ *
* @param Type of the value
* @param Type of the any expected exception that may occur during initialization
* @param reference A reference to a maybe not yet initialized value.
diff --git a/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java b/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java
index 70d439afe..a31807a8d 100644
--- a/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java
+++ b/main/commons/src/main/java/org/cryptomator/common/LicenseHolder.java
@@ -1,15 +1,15 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
+import org.cryptomator.common.settings.Settings;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
-import org.cryptomator.common.settings.Settings;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.util.Optional;
@Singleton
diff --git a/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java b/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
index 96588e6b5..e0ff9ae25 100644
--- a/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
+++ b/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
@@ -8,10 +8,10 @@
*******************************************************************************/
package org.cryptomator.common;
-import java.util.Comparator;
-
import org.apache.commons.lang3.StringUtils;
+import java.util.Comparator;
+
/**
* Compares version strings according to SemVer 2.0.0.
*/
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
similarity index 70%
rename from main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
rename to main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
index 300b5b035..537b83577 100644
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
+++ b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
@@ -1,60 +1,71 @@
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
import javafx.application.Platform;
+import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.util.Arrays;
-public class KeychainManager implements KeychainAccessStrategy {
+@Singleton
+public class KeychainManager implements KeychainAccessProvider {
- private static final Logger LOG = LoggerFactory.getLogger(KeychainManager.class);
+ private final ObjectExpression keychain;
+ private final LoadingCache passphraseStoredProperties;
- private final KeychainAccessStrategy keychain;
- private LoadingCache passphraseStoredProperties;
-
- KeychainManager(KeychainAccessStrategy keychain) {
- assert keychain.isSupported();
- this.keychain = keychain;
+ @Inject
+ KeychainManager(ObjectExpression selectedKeychain) {
+ this.keychain = selectedKeychain;
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
.weakValues() //
.build(CacheLoader.from(this::createStoredPassphraseProperty));
+ keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
+ }
+
+ private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
+ var result = keychain.getValue();
+ if (result == null) {
+ throw new NoKeychainAccessProviderException();
+ }
+ return result;
}
@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- keychain.storePassphrase(key, passphrase);
+ getKeychainOrFail().storePassphrase(key, passphrase);
setPassphraseStored(key, true);
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
- char[] passphrase = keychain.loadPassphrase(key);
+ char[] passphrase = getKeychainOrFail().loadPassphrase(key);
setPassphraseStored(key, passphrase != null);
return passphrase;
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
- keychain.deletePassphrase(key);
+ getKeychainOrFail().deletePassphrase(key);
setPassphraseStored(key, false);
}
@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- keychain.changePassphrase(key, passphrase);
+ getKeychainOrFail().changePassphrase(key, passphrase);
setPassphraseStored(key, true);
}
@Override
public boolean isSupported() {
- return true;
+ return keychain.getValue() != null;
}
/**
@@ -69,7 +80,7 @@ public class KeychainManager implements KeychainAccessStrategy {
public boolean isPassphraseStored(String key) throws KeychainAccessException {
char[] storedPw = null;
try {
- storedPw = keychain.loadPassphrase(key);
+ storedPw = getKeychainOrFail().loadPassphrase(key);
return storedPw != null;
} finally {
if (storedPw != null) {
@@ -77,14 +88,13 @@ public class KeychainManager implements KeychainAccessStrategy {
}
}
}
-
+
private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (property != null) {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
- LOG.warn("");
Platform.runLater(() -> property.set(value));
}
}
@@ -99,7 +109,7 @@ public class KeychainManager implements KeychainAccessStrategy {
*
* @param key The key to look up
* @return An observable property which is true when it almost certain that a password for key is stored.
- * @see #isPassphraseStored(String)
+ * @see #isPassphraseStored(String)
*/
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
return passphraseStoredProperties.getUnchecked(key);
@@ -107,7 +117,6 @@ public class KeychainManager implements KeychainAccessStrategy {
private BooleanProperty createStoredPassphraseProperty(String key) {
try {
- LOG.warn("LOAD"); // TODO remove
return new SimpleBooleanProperty(isPassphraseStored(key));
} catch (KeychainAccessException e) {
return new SimpleBooleanProperty(false);
diff --git a/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java
new file mode 100644
index 000000000..9ac343d36
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java
@@ -0,0 +1,44 @@
+package org.cryptomator.common.keychain;
+
+import dagger.Module;
+import dagger.Provides;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
+
+import javax.inject.Singleton;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.ObjectExpression;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Module
+public class KeychainModule {
+
+ @Provides
+ @Singleton
+ static Set> provideAvailableKeychainAccessProviderFactories() {
+ return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
+ }
+
+ @Provides
+ @Singleton
+ static Set provideSupportedKeychainAccessProviders(Set> availableFactories) {
+ return availableFactories.stream() //
+ .map(ServiceLoader.Provider::get) //
+ .filter(KeychainAccessProvider::isSupported) //
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ @Provides
+ @Singleton
+ static ObjectExpression provideKeychainAccessProvider(Settings settings, Set providers) {
+ return Bindings.createObjectBinding(() -> {
+ var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
+ var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
+ var fallbackProvider = providers.stream().findAny().orElse(null);
+ return selectedProvider.orElse(fallbackProvider);
+ }, settings.keychainBackend());
+ }
+
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java b/main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java
new file mode 100644
index 000000000..c14b81076
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java
@@ -0,0 +1,13 @@
+package org.cryptomator.common.keychain;
+
+import org.cryptomator.integrations.keychain.KeychainAccessException;
+
+/**
+ * Thrown by {@link KeychainManager} if attempted to access a keychain despite no supported keychain access provider being available.
+ */
+public class NoKeychainAccessProviderException extends KeychainAccessException {
+
+ public NoKeychainAccessProviderException() {
+ super("Did not find any supported keychain access provider.");
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java
new file mode 100644
index 000000000..7634601fa
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java
@@ -0,0 +1,36 @@
+package org.cryptomator.common.mountpoint;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.vaults.Volume;
+import org.cryptomator.common.vaults.WindowsDriveLetters;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+import java.util.Optional;
+
+public class AvailableDriveLetterChooser implements MountPointChooser {
+
+ public static final int PRIORITY = 200;
+
+ private final WindowsDriveLetters windowsDriveLetters;
+
+ @Inject
+ public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
+ this.windowsDriveLetters = windowsDriveLetters;
+ }
+
+ @Override
+ public boolean isApplicable(Volume caller) {
+ return SystemUtils.IS_OS_WINDOWS;
+ }
+
+ @Override
+ public Optional chooseMountPoint(Volume caller) {
+ return this.windowsDriveLetters.getAvailableDriveLetterPath();
+ }
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java
new file mode 100644
index 000000000..ac9e5b716
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java
@@ -0,0 +1,37 @@
+package org.cryptomator.common.mountpoint;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.vaults.Volume;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+public class CustomDriveLetterChooser implements MountPointChooser {
+
+ public static final int PRIORITY = 100;
+
+ private final VaultSettings vaultSettings;
+
+ @Inject
+ public CustomDriveLetterChooser(VaultSettings vaultSettings) {
+ this.vaultSettings = vaultSettings;
+ }
+
+ @Override
+ public boolean isApplicable(Volume caller) {
+ return SystemUtils.IS_OS_WINDOWS;
+ }
+
+ @Override
+ public Optional chooseMountPoint(Volume caller) {
+ return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
+ }
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java
new file mode 100644
index 000000000..6a86c654a
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java
@@ -0,0 +1,101 @@
+package org.cryptomator.common.mountpoint;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.Environment;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.settings.VolumeImpl;
+import org.cryptomator.common.vaults.Volume;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+public class CustomMountPointChooser implements MountPointChooser {
+
+ public static final int PRIORITY = 0;
+
+ private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
+
+ private final VaultSettings vaultSettings;
+ private final Environment environment;
+
+ @Inject
+ public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
+ this.vaultSettings = vaultSettings;
+ this.environment = environment;
+ }
+
+ @Override
+ public boolean isApplicable(Volume caller) {
+ //Disable if useExperimentalFuse is required (Win + Fuse), but set to false
+ return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
+ }
+
+ @Override
+ public Optional chooseMountPoint(Volume caller) {
+ //VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
+ return this.vaultSettings.getCustomMountPath().map(Paths::get);
+ }
+
+ @Override
+ public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
+ switch (caller.getMountPointRequirement()) {
+ case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint);
+ case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint);
+ case NONE -> {
+ //Requirement "NONE" doesn't make any sense here.
+ //No need to prepare/verify a Mountpoint without requiring one...
+ throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
+ }
+ default -> {
+ //Currently the case for "PARENT_OPT_MOUNT_POINT"
+ throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
+ }
+ }
+ LOG.debug("Successfully checked custom mount point: {}", mountPoint);
+ return false;
+ }
+
+ private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
+ //This the case on Windows when using FUSE
+ //See https://github.com/billziss-gh/winfsp/issues/320
+ Path parent = mountPoint.getParent();
+ if (!Files.isDirectory(parent)) {
+ throw new InvalidMountPointException(new NotDirectoryException(parent.toString()));
+ }
+ //We must use #notExists() here because notExists =/= !exists (see docs)
+ if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
+ //File exists OR can't be determined
+ throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString()));
+ }
+ }
+
+ private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
+ //This is the case for Windows when using Dokany and for Linux and Mac
+ if (!Files.isDirectory(mountPoint)) {
+ throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString()));
+ }
+ try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) {
+ if (ds.iterator().hasNext()) {
+ throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString()));
+ }
+ } catch (IOException exception) {
+ throw new InvalidMountPointException("IOException while checking folder content", exception);
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/InvalidMountPointException.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/InvalidMountPointException.java
new file mode 100644
index 000000000..b0c2fbd7a
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/InvalidMountPointException.java
@@ -0,0 +1,16 @@
+package org.cryptomator.common.mountpoint;
+
+public class InvalidMountPointException extends Exception {
+
+ public InvalidMountPointException(String message) {
+ super(message);
+ }
+
+ public InvalidMountPointException(Throwable cause) {
+ super(cause);
+ }
+
+ public InvalidMountPointException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java
new file mode 100644
index 000000000..2c40bb74a
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooser.java
@@ -0,0 +1,169 @@
+package org.cryptomator.common.mountpoint;
+
+import com.google.common.base.Preconditions;
+import org.cryptomator.common.vaults.Volume;
+
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.SortedSet;
+
+/**
+ * Base interface for the Mountpoint-Choosing-Operation that results in the choice and
+ * preparation of a mountpoint or an exception otherwise.
+ *
All MountPointChoosers (MPCs) need to implement this class and must be added to
+ * the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
+ * The MountPointChooserModule will sort them according to their {@link #getPriority() priority.}
+ * The priority must be defined by the developer to reflect a useful execution order.
+ * A specific priority must not be assigned to more than one MPC at a time;
+ * the result of having two MPCs with equal priority is undefined.
+ *
+ *
MPCs are executed by a {@link Volume} in ascending order of their priority
+ * (smaller priorities are tried first) to find and prepare a suitable mountpoint for the volume.
+ * The volume has access to a {@link SortedSet} of MPCs in this specific order,
+ * that is provided by the Module. The Set contains all available Choosers, even if they
+ * are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
+ * check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
+ * before calling {@code #chooseMountPoint(Volume)}.
+ *
+ *
At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
+ * according to the MPC's strategy. The strategy can involve reading configs,
+ * searching the filesystem, processing user-input or similar operations.
+ * If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
+ * {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
+ * MountPoint is verified and/or prepared. In this case no other MPC's will be called for
+ * this volume, even if {@code #prepare(Volume, Path)} fails.
+ *
+ *
If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
+ * without first calling the {@code #prepare(Volume, Path)} method of the current MPC.
+ * This is repeated until
+ *
+ *
either a mountpoint is returned by {@code #chooseMountPoint(Volume)}
+ * and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation
+ *
or no MPC remains and an {@link InvalidMountPointException} is thrown.
+ *
+ * If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
+ * Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
+ * before throwing the exception.
+ * If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
+ * remaining cleanup.
+ */
+public interface MountPointChooser extends Comparable {
+
+ /**
+ * Called by the {@link Volume} to determine whether this MountPointChooser is
+ * applicable for mounting the Vault/Volume, especially with regard to the
+ * current system configuration and particularities of the Volume type.
+ *
+ *
Developers should override this method to check for system configurations
+ * that are unsuitable for this MPC.
+ *
+ * @param caller The Volume that is calling the method to determine applicability of the MPC
+ * @return a boolean flag; true if applicable, else false.
+ * @see #chooseMountPoint(Volume)
+ */
+ boolean isApplicable(Volume caller);
+
+ /**
+ * Called by a {@link Volume} to choose a mountpoint according to the
+ * MountPointChoosers strategy.
+ *
+ *
This method must only be called for MPCs that were deemed
+ * {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
+ * Developers should override this method to find or extract a mountpoint for
+ * the volume without preparing it. Preparation should be done by
+ * {@link #prepare(Volume, Path)} instead.
+ * Exceptions in this method should be handled gracefully and result in returning
+ * {@link Optional#empty()} instead of throwing an exception.
+ *
+ * @param caller The Volume that is calling the method to choose a mountpoint
+ * @return the chosen path or {@link Optional#empty()} if an exception occurred
+ * or no mountpoint could be found.
+ * @see #isApplicable(Volume)
+ * @see #prepare(Volume, Path)
+ */
+ Optional chooseMountPoint(Volume caller);
+
+ /**
+ * Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.
+ * This method is only called if the {@link #chooseMountPoint(Volume)} method
+ * of the same MountPointChooser returned a path.
+ *
+ *
Developers should override this method to prepare the mountpoint for
+ * the volume and check for any obstacles that could hinder the mount operation.
+ * The mountpoint is deemed "prepared" if it can be used to mount a volume
+ * without any further filesystem actions or user interaction. If this is not possible,
+ * this method should fail. In other words: This method should not return without
+ * either failing or finalizing the preparation of the mountpoint.
+ * Generally speaking exceptions should be wrapped as
+ * {@link InvalidMountPointException} to allow efficient handling by the caller.
+ *
+ *
Often the preparation of a mountpoint involves creating files or others
+ * actions that require cleaning up after the volume is unmounted.
+ * In this case developers should override the {@link #cleanup(Volume, Path)}
+ * method and return {@code true} to the volume to indicate that the
+ * {@code #cleanup} method of this MPC should be called after unmount.
+ *
+ *
Please note: If this method fails the entire
+ * Mountpoint-Choosing-Operation is aborted without calling
+ * {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
+ * do all necessary cleanup before throwing the exception.
+ *
+ * @param caller The Volume that is calling the method to prepare a mountpoint
+ * @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
+ * @return a boolean flag; true if cleanup is needed, false otherwise
+ * @throws InvalidMountPointException if the preparation fails
+ * @see #chooseMountPoint(Volume)
+ * @see #cleanup(Volume, Path)
+ */
+ default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
+ return false; //NO-OP
+ }
+
+ /**
+ * Called by a {@link Volume} to do any cleanup needed after unmount.
+ *
+ *
This method is only called if the {@link #prepare(Volume, Path)} method
+ * of the same MountPointChooser returned {@code true}. Typically developers want to
+ * delete any files created prior to mount or do similar tasks.
+ * Exceptions in this method should be handled gracefully.
+ *
+ * @param caller The Volume that is calling the method to cleanup the prepared mountpoint
+ * @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
+ * @see #prepare(Volume, Path)
+ */
+ default void cleanup(Volume caller, Path mountPoint) {
+ //NO-OP
+ }
+
+ /**
+ * Called by the {@link MountPointChooserModule} to sort the available MPCs
+ * and determine their execution order.
+ * The priority must be defined by the developer to reflect a useful execution order.
+ * MPCs with lower priorities will be placed at lower indices in the resulting
+ * {@link SortedSet} and will be executed with higher probability.
+ * A specific priority must not be assigned to more than one MPC at a time;
+ * the result of having two MPCs with equal priority is undefined.
+ *
+ * @return the priority of this MPC.
+ */
+ int getPriority();
+
+ /**
+ * Called by the {@link Volume} to determine the execution order of the registered MPCs.
+ * Implementations usually may not override this method. This default implementation
+ * sorts the MPCs in ascending order of their {@link #getPriority() priority.}
+ *
+ * Original description:
+ *
{@inheritDoc}
+ *
+ * @implNote This default implementation sorts the MPCs in ascending order
+ * of their {@link #getPriority() priority.}
+ */
+ @Override
+ default int compareTo(MountPointChooser other) {
+ Preconditions.checkNotNull(other, "Other must not be null!");
+
+ //Sort by priority (ascending order)
+ return Integer.compare(this.getPriority(), other.getPriority());
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java
new file mode 100644
index 000000000..09789dacb
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointChooserModule.java
@@ -0,0 +1,50 @@
+package org.cryptomator.common.mountpoint;
+
+import com.google.common.collect.ImmutableSortedSet;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+import org.cryptomator.common.vaults.PerVault;
+
+import javax.inject.Named;
+import java.util.Set;
+import java.util.SortedSet;
+
+/**
+ * Dagger-Module for {@link MountPointChooser MountPointChoosers.}
+ * See there for additional information.
+ *
+ * @see MountPointChooser
+ */
+@Module
+public abstract class MountPointChooserModule {
+
+ @Binds
+ @IntoSet
+ @PerVault
+ public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
+
+ @Binds
+ @IntoSet
+ @PerVault
+ public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
+
+ @Binds
+ @IntoSet
+ @PerVault
+ public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
+
+ @Binds
+ @IntoSet
+ @PerVault
+ public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
+
+ @Provides
+ @PerVault
+ @Named("orderedMountPointChoosers")
+ public static SortedSet provideOrderedMountPointChoosers(Set choosers) {
+ //Sort by natural order. The natural order is defined by MountPointChooser#compareTo
+ return ImmutableSortedSet.copyOf(choosers);
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java
new file mode 100644
index 000000000..60991b97e
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java
@@ -0,0 +1,110 @@
+package org.cryptomator.common.mountpoint;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.Environment;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.vaults.Volume;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+public class TemporaryMountPointChooser implements MountPointChooser {
+
+ public static final int PRIORITY = 300;
+
+ private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
+ private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
+
+ private final VaultSettings vaultSettings;
+ private final Environment environment;
+
+ @Inject
+ public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
+ this.vaultSettings = vaultSettings;
+ this.environment = environment;
+ }
+
+ @Override
+ public boolean isApplicable(Volume caller) {
+ if (this.environment.getMountPointsDir().isEmpty()) {
+ LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Optional chooseMountPoint(Volume caller) {
+ return this.environment.getMountPointsDir().map(this::choose);
+ }
+
+ private Path choose(Path parent) {
+ String basename = this.vaultSettings.mountName().get();
+ for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
+ Path mountPoint = parent.resolve(basename + "_" + i);
+ if (Files.notExists(mountPoint)) {
+ return mountPoint;
+ }
+ }
+ LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parent, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
+ return null;
+ }
+
+ @Override
+ public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
+ // https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
+ // In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
+ // starting with version 3.5.0, FUSE will create non-existent mount points automatically.
+ if (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
+ return false;
+ }
+
+ try {
+ switch (caller.getMountPointRequirement()) {
+ case PARENT_NO_MOUNT_POINT -> {
+ Files.createDirectories(mountPoint.getParent());
+ LOG.debug("Successfully created folder for mount point: {}", mountPoint);
+ return false;
+ }
+ case EMPTY_MOUNT_POINT -> {
+ Files.createDirectories(mountPoint);
+ LOG.debug("Successfully created mount point: {}", mountPoint);
+ return true;
+ }
+ case NONE -> {
+ //Requirement "NONE" doesn't make any sense here.
+ //No need to prepare/verify a Mountpoint without requiring one...
+ throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
+ }
+ default -> {
+ //Currently the case for "PARENT_OPT_MOUNT_POINT"
+ throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
+ }
+ }
+ } catch (IOException exception) {
+ throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
+ }
+ }
+
+ @Override
+ public void cleanup(Volume caller, Path mountPoint) {
+ try {
+ Files.delete(mountPoint);
+ LOG.debug("Successfully deleted mount point: {}", mountPoint);
+ } catch (IOException e) {
+ LOG.warn("Could not delete mount point: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java b/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java
new file mode 100644
index 000000000..65f869a12
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java
@@ -0,0 +1,19 @@
+package org.cryptomator.common.settings;
+
+public enum KeychainBackend {
+ GNOME("org.cryptomator.linux.keychain.SecretServiceKeychainAccess"),
+ KDE("org.cryptomator.linux.keychain.KDEWalletKeychainAccess"),
+ MAC_SYSTEM_KEYCHAIN("org.cryptomator.macos.keychain.MacSystemKeychainAccess"),
+ WIN_SYSTEM_KEYCHAIN("org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess");
+
+ private final String providerClass;
+
+ KeychainBackend(String providerClass) {
+ this.providerClass = providerClass;
+ }
+
+ public String getProviderClass() {
+ return providerClass;
+ }
+
+}
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 16d16b01d..e50391d2d 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,6 +8,8 @@
******************************************************************************/
package org.cryptomator.common.settings;
+import org.apache.commons.lang3.SystemUtils;
+
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
@@ -20,7 +22,6 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.NodeOrientation;
-
import java.util.function.Consumer;
public class Settings {
@@ -34,8 +35,9 @@ public class Settings {
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
public static final boolean DEFAULT_DEBUG_MODE = false;
- public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
+ public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
+ public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
private static final String DEFAULT_LICENSE_KEY = "";
@@ -49,6 +51,7 @@ public class Settings {
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
private final ObjectProperty theme = new SimpleObjectProperty<>(DEFAULT_THEME);
+ private final ObjectProperty keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
private final ObjectProperty userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
@@ -68,6 +71,7 @@ public class Settings {
debugMode.addListener(this::somethingChanged);
preferredVolumeImpl.addListener(this::somethingChanged);
theme.addListener(this::somethingChanged);
+ keychainBackend.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
licenseKey.addListener(this::somethingChanged);
}
@@ -75,7 +79,7 @@ public class Settings {
void setSaveCmd(Consumer saveCmd) {
this.saveCmd = saveCmd;
}
-
+
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
this.save();
}
@@ -99,7 +103,7 @@ public class Settings {
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
-
+
public BooleanProperty startHidden() {
return startHidden;
}
@@ -128,6 +132,8 @@ public class Settings {
return theme;
}
+ public ObjectProperty keychainBackend() { return keychainBackend; }
+
public ObjectProperty userInterfaceOrientation() {
return userInterfaceOrientation;
}
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 395034f56..3b06b8a0b 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
@@ -9,10 +9,10 @@ import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
-import javafx.geometry.NodeOrientation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javafx.geometry.NodeOrientation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter {
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
+ out.name("keychainBackend").value(value.keychainBackend().get().name());
out.name("licenseKey").value(value.licenseKey().get());
out.endObject();
}
@@ -69,6 +70,7 @@ public class SettingsJsonAdapter extends TypeAdapter {
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
+ case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
case "licenseKey" -> settings.licenseKey().set(in.nextString());
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
@@ -108,6 +110,15 @@ public class SettingsJsonAdapter extends TypeAdapter {
}
}
+ private KeychainBackend parseKeychainBackend(String backendName) {
+ try {
+ return KeychainBackend.valueOf(backendName.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Invalid keychain backend {}. Defaulting to {}.", backendName, Settings.DEFAULT_KEYCHAIN_BACKEND);
+ return Settings.DEFAULT_KEYCHAIN_BACKEND;
+ }
+ }
+
private NodeOrientation parseUiOrientation(String uiOrientationName) {
try {
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java b/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java
index a8b66c65c..8df1e7f89 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java
@@ -1,11 +1,21 @@
package org.cryptomator.common.settings;
-public enum UiTheme {
- LIGHT("preferences.general.theme.light"),
- DARK("preferences.general.theme.dark");
- // CUSTOM("Custom (%s)");
+import org.apache.commons.lang3.SystemUtils;
- private String displayName;
+public enum UiTheme {
+ LIGHT("preferences.general.theme.light"), //
+ DARK("preferences.general.theme.dark"), //
+ AUTOMATIC("preferences.general.theme.automatic");
+
+ public static UiTheme[] applicableValues() {
+ if (SystemUtils.IS_OS_MAC) {
+ return values();
+ } else {
+ return new UiTheme[]{LIGHT, DARK};
+ }
+ }
+
+ private final String displayName;
UiTheme(String displayName) {
this.displayName = displayName;
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 1a1dfe140..c99da6325 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
@@ -7,7 +7,11 @@ package org.cryptomator.common.settings;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
+import org.apache.commons.lang3.StringUtils;
+
import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@@ -16,9 +20,6 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
-import org.apache.commons.lang3.StringUtils;
-import org.fxmisc.easybind.EasyBind;
-
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
@@ -34,15 +35,14 @@ public class VaultSettings {
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
- public static final String DEFAULT_MOUNT_NAME = "Vault";
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
- private static final Random RNG = new Random();
+ private static final Random RNG = new Random();
private final String id;
private final ObjectProperty path = new SimpleObjectProperty();
- private final StringProperty mountName = new SimpleStringProperty();
+ private final StringProperty displayName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
@@ -53,24 +53,15 @@ public class VaultSettings {
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
private final ObjectProperty actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
+ private final StringBinding mountName;
+
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
-
- EasyBind.subscribe(path, this::deriveMountNameFromPathOrUseDefault);
+ this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
}
Observable[] observables() {
- return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
- }
-
- private void deriveMountNameFromPathOrUseDefault(Path path) {
- if (StringUtils.isBlank(mountName.get())) {
- if (path != null && path.getFileName() != null) {
- mountName.set(normalizeMountName(path.getFileName().toString()));
- } else {
- mountName.set(DEFAULT_MOUNT_NAME);
- }
- }
+ return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
}
public static VaultSettings withRandomId() {
@@ -83,8 +74,9 @@ public class VaultSettings {
return BaseEncoding.base64Url().encode(randomBytes);
}
- public static String normalizeMountName(String mountName) {
- String normalizedMountName = StringUtils.stripAccents(mountName);
+ //visible for testing
+ String normalizeDisplayName() {
+ String normalizedMountName = StringUtils.stripAccents(displayName.get());
StringBuilder builder = new StringBuilder();
for (char c : normalizedMountName.toCharArray()) {
if (Character.isWhitespace(c)) {
@@ -112,7 +104,11 @@ public class VaultSettings {
return path;
}
- public StringProperty mountName() {
+ public StringProperty displayName() {
+ return displayName;
+ }
+
+ public StringBinding mountName() {
return mountName;
}
@@ -120,6 +116,14 @@ public class VaultSettings {
return winDriveLetter;
}
+ public Optional getWinDriveLetter() {
+ String current = this.winDriveLetter.get();
+ if (!Strings.isNullOrEmpty(current)) {
+ return Optional.of(current);
+ }
+ return Optional.empty();
+ }
+
public BooleanProperty unlockAfterStartup() {
return unlockAfterStartup;
}
@@ -151,7 +155,7 @@ public class VaultSettings {
public StringProperty mountFlags() {
return mountFlags;
}
-
+
public IntegerProperty filenameLengthLimit() {
return filenameLengthLimit;
}
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 d136ffaff..04a352a49 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
@@ -21,7 +21,7 @@ class VaultSettingsJsonAdapter {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
- out.name("mountName").value(value.mountName().get());
+ out.name("displayName").value(value.displayName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
@@ -37,7 +37,8 @@ class VaultSettingsJsonAdapter {
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
- String mountName = null;
+ String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
+ String displayName = null;
String customMountPath = null;
String winDriveLetter = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
@@ -54,7 +55,8 @@ class VaultSettingsJsonAdapter {
switch (name) {
case "id" -> id = in.nextString();
case "path" -> path = in.nextString();
- case "mountName" -> mountName = in.nextString();
+ case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
+ case "displayName" -> displayName = in.nextString();
case "winDriveLetter" -> winDriveLetter = in.nextString();
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
@@ -73,7 +75,11 @@ class VaultSettingsJsonAdapter {
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
- vaultSettings.mountName().set(mountName);
+ if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
+ vaultSettings.displayName().set(displayName);
+ } else {
+ vaultSettings.displayName().set(mountName);
+ }
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/WebDavUrlScheme.java b/main/commons/src/main/java/org/cryptomator/common/settings/WebDavUrlScheme.java
index 0dcb4ac9d..a72d11968 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/WebDavUrlScheme.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/WebDavUrlScheme.java
@@ -7,7 +7,8 @@ public enum WebDavUrlScheme {
private final String prefix;
private final String displayName;
- WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
+ WebDavUrlScheme(String prefix, String displayName) {
+ this.prefix = prefix;
this.displayName = displayName;
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java
new file mode 100644
index 000000000..91c6e22e4
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java
@@ -0,0 +1,60 @@
+package org.cryptomator.common.vaults;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import org.cryptomator.common.mountpoint.InvalidMountPointException;
+import org.cryptomator.common.mountpoint.MountPointChooser;
+
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public abstract class AbstractVolume implements Volume {
+
+ private final SortedSet choosers;
+
+ protected Path mountPoint;
+
+ //Cleanup
+ private boolean cleanupRequired;
+ private MountPointChooser usedChooser;
+
+ public AbstractVolume(SortedSet choosers) {
+ this.choosers = choosers;
+ }
+
+ protected Path determineMountPoint() throws InvalidMountPointException {
+ SortedSet checkedChoosers = new TreeSet<>(); //Natural order
+ for (MountPointChooser chooser : this.choosers) {
+ if (!chooser.isApplicable(this)) {
+ continue;
+ }
+
+ Optional chosenPath = chooser.chooseMountPoint(this);
+ checkedChoosers.add(chooser); //Consider a chooser checked if it's #chooseMountPoint() method was called
+ if (chosenPath.isEmpty()) {
+ //Chooser was applicable, but couldn't find a feasible mountpoint
+ continue;
+ }
+ this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs
+ this.usedChooser = chooser;
+ return chosenPath.get();
+ }
+ //SortedSet#stream() should return a sorted stream (that's what it's docs and the docs of #spliterator() say, even if they are not 100% clear for me.)
+ //We want to keep that order, that's why we use ImmutableSet#toImmutableSet() to collect (even if it doesn't implement SortedSet, it's docs promise use encounter ordering.)
+ String checked = Joiner.on(", ").join(checkedChoosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet()));
+ throw new InvalidMountPointException(String.format("No feasible MountPoint found! Checked %s", checked.isBlank() ? "" : checked));
+ }
+
+ protected void cleanupMountPoint() {
+ if (this.cleanupRequired) {
+ this.usedChooser.cleanup(this, this.mountPoint);
+ }
+ }
+
+ @Override
+ public Optional getMountPoint() {
+ return Optional.ofNullable(mountPoint);
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java b/main/commons/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
index ea2cdfa0a..68b61688b 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/DefaultMountFlags.java
@@ -10,4 +10,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
public @interface DefaultMountFlags {
+
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
index 1bfd813e3..644604b4b 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
@@ -1,7 +1,9 @@
package org.cryptomator.common.vaults;
-import com.google.common.base.Strings;
+import org.cryptomator.common.mountpoint.InvalidMountPointException;
+import org.cryptomator.common.mountpoint.MountPointChooser;
import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.dokany.Mount;
import org.cryptomator.frontend.dokany.MountFactory;
@@ -10,46 +12,39 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import java.io.IOException;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.NotDirectoryException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
+import javax.inject.Named;
+import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
-public class DokanyVolume implements Volume {
+public class DokanyVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
- private static final String FS_TYPE_NAME = "Cryptomator File System";
+ private static final String FS_TYPE_NAME = "CryptomatorFS";
private final VaultSettings vaultSettings;
private final MountFactory mountFactory;
- private final WindowsDriveLetters windowsDriveLetters;
+
private Mount mount;
- private Path mountPoint;
@Inject
- public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
+ public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet choosers) {
+ super(choosers);
this.vaultSettings = vaultSettings;
this.mountFactory = new MountFactory(executorService);
- this.windowsDriveLetters = windowsDriveLetters;
}
@Override
- public boolean isSupported() {
- return DokanyVolume.isSupportedStatic();
+ public VolumeImpl getImplementationType() {
+ return VolumeImpl.DOKANY;
}
@Override
- public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
+ public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
- String mountName = vaultSettings.mountName().get();
+ String mountName = vaultSettings.displayName().get();
try {
- this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
+ this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
} catch (MountFailedException e) {
if (vaultSettings.getCustomMountPath().isPresent()) {
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
@@ -58,36 +53,6 @@ public class DokanyVolume implements Volume {
}
}
- private Path determineMountPoint() throws VolumeException, IOException {
- Optional optionalCustomMountPoint = vaultSettings.getCustomMountPath();
- if (optionalCustomMountPoint.isPresent()) {
- Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
- checkProvidedMountPoint(customMountPoint);
- return customMountPoint;
- } else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
- return Path.of(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
- } else {
- //auto assign drive letter
- if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
- return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
- } else {
- //TODO: Error Handling
- throw new VolumeException("No free drive letter available.");
- }
- }
- }
-
- private void checkProvidedMountPoint(Path mountPoint) throws IOException {
- if (!Files.isDirectory(mountPoint)) {
- throw new NotDirectoryException(mountPoint.toString());
- }
- try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) {
- if (ds.iterator().hasNext()) {
- throw new DirectoryNotEmptyException(mountPoint.toString());
- }
- }
- }
-
@Override
public void reveal() throws VolumeException {
boolean success = mount.reveal();
@@ -99,11 +64,17 @@ public class DokanyVolume implements Volume {
@Override
public void unmount() {
mount.close();
+ cleanupMountPoint();
}
@Override
- public Optional getMountPoint() {
- return Optional.ofNullable(mountPoint);
+ public boolean isSupported() {
+ return DokanyVolume.isSupportedStatic();
+ }
+
+ @Override
+ public MountPointRequirement getMountPointRequirement() {
+ return MountPointRequirement.EMPTY_MOUNT_POINT;
}
public static boolean isSupportedStatic() {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java
index 4b6918be9..76a83983b 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/FuseVolume.java
@@ -2,8 +2,9 @@ package org.cryptomator.common.vaults;
import com.google.common.base.Splitter;
import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.Environment;
-import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.mountpoint.InvalidMountPointException;
+import org.cryptomator.common.mountpoint.MountPointChooser;
+import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
@@ -15,95 +16,36 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import java.io.IOException;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.NotDirectoryException;
+import javax.inject.Named;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
+import java.util.SortedSet;
-public class FuseVolume implements Volume {
+public class FuseVolume extends AbstractVolume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
- private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
- private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
- private final VaultSettings vaultSettings;
- private final Environment environment;
-
- private Mount fuseMnt;
- private Path mountPoint;
- private boolean createdTemporaryMountPoint;
+ private Mount mount;
@Inject
- public FuseVolume(VaultSettings vaultSettings, Environment environment) {
- this.vaultSettings = vaultSettings;
- this.environment = environment;
+ public FuseVolume(@Named("orderedMountPointChoosers") SortedSet choosers) {
+ super(choosers);
}
@Override
- public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
- Optional optionalCustomMountPoint = vaultSettings.getCustomMountPath();
- if (optionalCustomMountPoint.isPresent()) {
- Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
- checkProvidedMountPoint(customMountPoint);
- this.mountPoint = customMountPoint;
- LOG.debug("Successfully checked custom mount point: {}", mountPoint);
- } else {
- this.mountPoint = prepareTemporaryMountPoint();
- LOG.debug("Successfully created mount point: {}", mountPoint);
- }
+ public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
+ this.mountPoint = determineMountPoint();
+
mount(fs.getPath("/"), mountFlags);
}
- private void checkProvidedMountPoint(Path mountPoint) throws IOException {
- if (!Files.isDirectory(mountPoint)) {
- throw new NotDirectoryException(mountPoint.toString());
- }
- try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) {
- if (ds.iterator().hasNext()) {
- throw new DirectoryNotEmptyException(mountPoint.toString());
- }
- }
- }
-
- private Path prepareTemporaryMountPoint() throws IOException, VolumeException {
- Path mountPoint = chooseNonExistingTemporaryMountPoint();
- // https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
- // In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
- // starting with version 3.5.0, FUSE will create non-existent mount points automatically.
- if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
- return mountPoint;
- } else {
- Files.createDirectories(mountPoint);
- this.createdTemporaryMountPoint = true;
- return mountPoint;
- }
- }
-
- private Path chooseNonExistingTemporaryMountPoint() throws VolumeException {
- Path parent = environment.getMountPointsDir().orElseThrow();
- String basename = vaultSettings.getId();
- for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
- Path mountPoint = parent.resolve(basename + "_" + i);
- if (Files.notExists(mountPoint)) {
- return mountPoint;
- }
- }
- LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
- throw new VolumeException("Did not find feasible mount point.");
- }
-
private void mount(Path root, String mountFlags) throws VolumeException {
try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create() //
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
.build();
- this.fuseMnt = mounter.mount(root, envVars);
- } catch (CommandFailedException e) {
+ this.mount = mounter.mount(root, envVars);
+ } catch (CommandFailedException | FuseNotSupportedException e) {
throw new VolumeException("Unable to mount Filesystem", e);
}
}
@@ -115,7 +57,7 @@ public class FuseVolume implements Volume {
@Override
public void reveal() throws VolumeException {
try {
- fuseMnt.revealInFileManager();
+ mount.revealInFileManager();
} catch (CommandFailedException e) {
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
throw new VolumeException(e);
@@ -130,34 +72,23 @@ public class FuseVolume implements Volume {
@Override
public synchronized void unmountForced() throws VolumeException {
try {
- fuseMnt.unmountForced();
- fuseMnt.close();
+ mount.unmountForced();
+ mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
- cleanupTemporaryMountPoint();
+ cleanupMountPoint();
}
@Override
public synchronized void unmount() throws VolumeException {
try {
- fuseMnt.unmount();
- fuseMnt.close();
+ mount.unmount();
+ mount.close();
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
- cleanupTemporaryMountPoint();
- }
-
- private void cleanupTemporaryMountPoint() {
- if (createdTemporaryMountPoint) {
- try {
- Files.delete(mountPoint);
- LOG.debug("Successfully deleted mount point: {}", mountPoint);
- } catch (IOException e) {
- LOG.warn("Could not delete mount point: {}", e.getMessage());
- }
- }
+ cleanupMountPoint();
}
@Override
@@ -166,12 +97,17 @@ public class FuseVolume implements Volume {
}
@Override
- public Optional getMountPoint() {
- return Optional.ofNullable(mountPoint);
+ public VolumeImpl getImplementationType() {
+ return VolumeImpl.FUSE;
+ }
+
+ @Override
+ public MountPointRequirement getMountPointRequirement() {
+ return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT;
}
public static boolean isSupportedStatic() {
- return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
+ return FuseMountFactory.isFuseSupported();
}
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java b/main/commons/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java
new file mode 100644
index 000000000..84a798e59
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java
@@ -0,0 +1,28 @@
+package org.cryptomator.common.vaults;
+
+/**
+ * Enumeration used to indicate the requirements for mounting a vault
+ * using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
+ */
+public enum MountPointRequirement {
+
+ /**
+ * No Mountpoint on the local filesystem required. (e.g. WebDAV)
+ */
+ NONE,
+
+ /**
+ * A parent folder is required, but the actual Mountpoint must not exist.
+ */
+ PARENT_NO_MOUNT_POINT,
+
+ /**
+ * A parent folder is required, but the actual Mountpoint may exist.
+ */
+ PARENT_OPT_MOUNT_POINT,
+
+ /**
+ * The actual Mountpoint must exist and must be empty.
+ */
+ EMPTY_MOUNT_POINT;
+}
\ No newline at end of file
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/PerVault.java b/main/commons/src/main/java/org/cryptomator/common/vaults/PerVault.java
index dab895b37..692349b61 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/PerVault.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/PerVault.java
@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@interface PerVault {
+public @interface PerVault {
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
index 4a8d1e565..f2ab973f6 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
@@ -9,16 +9,11 @@
package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
-import javafx.beans.Observable;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.BooleanBinding;
-import javafx.beans.binding.StringBinding;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleBooleanProperty;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LazyInitializer;
+import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.vaults.Volume.VolumeException;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
@@ -33,13 +28,20 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
-import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
@@ -58,7 +60,7 @@ public class Vault {
private final ObjectProperty state;
private final ObjectProperty lastKnownException;
private final VaultStats stats;
- private final StringBinding displayableName;
+ private final StringBinding displayName;
private final StringBinding displayablePath;
private final BooleanBinding locked;
private final BooleanBinding processing;
@@ -81,7 +83,7 @@ public class Vault {
this.state = state;
this.lastKnownException = lastKnownException;
this.stats = stats;
- this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
+ this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
@@ -124,16 +126,13 @@ public class Vault {
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
- public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
- if (vaultSettings.useCustomMountPath().get() && Strings.isNullOrEmpty(vaultSettings.customMountPath().get())) {
- throw new NotDirectoryException("");
- }
+ public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags());
}
- public synchronized void lock(boolean forced) throws Volume.VolumeException {
+ public synchronized void lock(boolean forced) throws VolumeException {
if (forced && volume.supportsForcedUnmount()) {
volume.unmountForced();
} else {
@@ -149,7 +148,7 @@ public class Vault {
}
}
- public void reveal() throws Volume.VolumeException {
+ public void reveal() throws VolumeException {
volume.reveal();
}
@@ -229,13 +228,12 @@ public class Vault {
return state.get() == VaultState.ERROR;
}
- public StringBinding displayableNameProperty() {
- return displayableName;
+ public StringBinding displayNameProperty() {
+ return displayName;
}
- public String getDisplayableName() {
- Path p = vaultSettings.path().get();
- return p.getFileName().toString();
+ public String getDisplayName() {
+ return vaultSettings.displayName().get();
}
public StringBinding accessPointProperty() {
@@ -332,6 +330,10 @@ public class Vault {
return vaultSettings.getId();
}
+ public Optional getVolume() {
+ return Optional.ofNullable(this.volume);
+ }
+
// ******************************************************************************
// Hashcode / Equals
// *******************************************************************************/
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultComponent.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultComponent.java
index 98a9f951d..debfab3ed 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultComponent.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultComponent.java
@@ -6,15 +6,15 @@
package org.cryptomator.common.vaults;
import dagger.BindsInstance;
-import org.cryptomator.common.settings.VaultSettings;
-
import dagger.Subcomponent;
+import org.cryptomator.common.mountpoint.MountPointChooserModule;
+import org.cryptomator.common.settings.VaultSettings;
import javax.annotation.Nullable;
import javax.inject.Named;
@PerVault
-@Subcomponent(modules = {VaultModule.class})
+@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
public interface VaultComponent {
Vault vault();
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListChangeListener.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListChangeListener.java
index 147d6cc45..a7e422115 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListChangeListener.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListChangeListener.java
@@ -1,9 +1,9 @@
package org.cryptomator.common.vaults;
+import org.cryptomator.common.settings.VaultSettings;
+
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import org.cryptomator.common.settings.VaultSettings;
-
import java.util.List;
import java.util.stream.Collectors;
@@ -20,7 +20,7 @@ class VaultListChangeListener implements ListChangeListener {
@Override
public void onChanged(Change extends Vault> c) {
- while(c.next()) {
+ while (c.next()) {
if (c.wasAdded()) {
List addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
vaultSettingsList.addAll(c.getFrom(), addedSettings);
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
index 75a48c6fc..984008ba9 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -19,12 +17,14 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
+import java.util.ResourceBundle;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -36,10 +36,12 @@ public class VaultListManager {
private final VaultComponent.Builder vaultComponentBuilder;
private final ObservableList vaultList;
+ private final String defaultVaultName;
@Inject
- public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
+ public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
this.vaultComponentBuilder = vaultComponentBuilder;
+ this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
this.vaultList = FXCollections.observableArrayList(Vault::observables);
addAll(settings.getDirectories());
@@ -59,14 +61,23 @@ public class VaultListManager {
if (alreadyExistingVault.isPresent()) {
return alreadyExistingVault.get();
} else {
- VaultSettings vaultSettings = VaultSettings.withRandomId();
- vaultSettings.path().set(normalizedPathToVault);
- Vault newVault = create(vaultSettings);
+ Vault newVault = create(newVaultSettings(normalizedPathToVault));
vaultList.add(newVault);
return newVault;
}
}
+ private VaultSettings newVaultSettings(Path path) {
+ VaultSettings vaultSettings = VaultSettings.withRandomId();
+ vaultSettings.path().set(path);
+ if (path.getFileName() != null) {
+ vaultSettings.displayName().set(path.getFileName().toString());
+ } else {
+ vaultSettings.displayName().set(defaultVaultName);
+ }
+ return vaultSettings;
+ }
+
private void addAll(Collection vaultSettings) {
Collection vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
vaultList.addAll(vaults);
@@ -92,7 +103,7 @@ public class VaultListManager {
}
return compBuilder.build().vault();
}
-
+
public static VaultState redetermineVaultState(Vault vault) {
VaultState previousState = vault.getState();
return switch (previousState) {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
index 1afd9d88b..4844143a3 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
@@ -7,14 +7,6 @@ package org.cryptomator.common.vaults;
import dagger.Module;
import dagger.Provides;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.StringBinding;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.ReadOnlyBooleanProperty;
-import javafx.beans.property.ReadOnlyStringProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@@ -25,6 +17,12 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Named;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -77,7 +75,7 @@ public class VaultModule {
@DefaultMountFlags
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
ObjectProperty preferredVolumeImpl = settings.preferredVolumeImpl();
- StringProperty mountName = vaultSettings.mountName();
+ StringBinding mountName = vaultSettings.mountName();
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
return Bindings.createStringBinding(() -> {
@@ -86,6 +84,8 @@ public class VaultModule {
return getMacFuseDefaultMountFlags(mountName, readOnly);
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
return getLinuxFuseDefaultMountFlags(readOnly);
+ } else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
+ return getWindowsFuseDefaultMountFlags(mountName, readOnly);
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
return getDokanyDefaultMountFlags(readOnly);
} else {
@@ -95,7 +95,7 @@ public class VaultModule {
}
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
- private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) {
+ private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_MAC_OSX;
StringBuilder flags = new StringBuilder();
if (readOnly.get()) {
@@ -144,6 +144,28 @@ public class VaultModule {
return flags.toString().strip();
}
+ // see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
+ // see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
+ // see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were choosen
+ private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
+ assert SystemUtils.IS_OS_WINDOWS;
+ StringBuilder flags = new StringBuilder();
+
+ //WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
+ //So for the time being not setting them is the way to go...
+ //See: https://github.com/billziss-gh/winfsp/issues/319
+ if (!readOnly.get()) {
+ flags.append(" -ouid=-1");
+ flags.append(" -ogid=-1");
+ }
+ flags.append(" -ovolname=").append(mountName.get());
+ //Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
+ //but the option exists. Let's keep this here in case we need it.
+// flags.append(" -oThreadCount=").append(5);
+
+ return flags.toString().strip();
+ }
+
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
assert SystemUtils.IS_OS_WINDOWS;
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultState.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultState.java
index a25ce59fa..fa5e6c295 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultState.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultState.java
@@ -2,7 +2,7 @@ package org.cryptomator.common.vaults;
public enum VaultState {
/**
- * No vault found at the provided path
+ * No vault found at the provided path
*/
MISSING,
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultStats.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultStats.java
index c3b3cc5ab..46cf31991 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultStats.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultStats.java
@@ -1,5 +1,11 @@
package org.cryptomator.common.vaults;
+import org.cryptomator.cryptofs.CryptoFileSystem;
+import org.cryptomator.cryptofs.CryptoFileSystemStats;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
@@ -10,12 +16,6 @@ import javafx.beans.property.SimpleLongProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
-import org.cryptomator.cryptofs.CryptoFileSystem;
-import org.cryptomator.cryptofs.CryptoFileSystemStats;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@@ -90,6 +90,10 @@ public class VaultStats {
private class UpdateStatsService extends ScheduledService> {
+ private UpdateStatsService() {
+ setOnFailed(event -> LOG.error("Error in UpdateStateService.", getException()));
+ }
+
@Override
protected Task> createTask() {
return new Task<>() {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/Volume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/Volume.java
index a0aa11be8..624efea13 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/Volume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/Volume.java
@@ -1,5 +1,6 @@
package org.cryptomator.common.vaults;
+import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
@@ -20,11 +21,18 @@ public interface Volume {
*/
boolean isSupported();
+ /**
+ * Gets the coresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
+ *
+ * @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
+ */
+ VolumeImpl getImplementationType();
+
/**
* @param fs
* @throws IOException
*/
- void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException;
+ void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
void reveal() throws VolumeException;
@@ -32,6 +40,8 @@ public interface Volume {
Optional getMountPoint();
+ MountPointRequirement getMountPointRequirement();
+
// optional forced unmounting:
default boolean supportsForcedUnmount() {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/WebDavVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/WebDavVolume.java
index a12236230..bf42e353f 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/WebDavVolume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/WebDavVolume.java
@@ -3,6 +3,7 @@ package org.cryptomator.common.vaults;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.mount.MountParams;
@@ -98,7 +99,12 @@ public class WebDavVolume implements Volume {
@Override
public Optional getMountPoint() {
- return Optional.ofNullable(mountPoint);
+ return Optional.ofNullable(mountPoint); //TODO
+ }
+
+ @Override
+ public MountPointRequirement getMountPointRequirement() {
+ return MountPointRequirement.NONE;
}
private String getLocalhostAliasOrNull() {
@@ -126,6 +132,11 @@ public class WebDavVolume implements Volume {
return WebDavVolume.isSupportedStatic();
}
+ @Override
+ public VolumeImpl getImplementationType() {
+ return VolumeImpl.WEBDAV;
+ }
+
@Override
public boolean supportsForcedUnmount() {
return mount != null && mount.forced().isPresent();
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java b/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java
index 016c39a3a..f3ef2c7dc 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/WindowsDriveLetters.java
@@ -5,6 +5,7 @@
*******************************************************************************/
package org.cryptomator.common.vaults;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils;
@@ -12,6 +13,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.FileSystems;
import java.nio.file.Path;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -24,7 +26,7 @@ public final class WindowsDriveLetters {
static {
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
- C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
+ C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
}
}
@@ -46,7 +48,18 @@ public final class WindowsDriveLetters {
}
public Set getAvailableDriveLetters() {
- return Sets.difference(C_TO_Z, getOccupiedDriveLetters());
+ return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
}
+ public Optional getAvailableDriveLetter() {
+ return getAvailableDriveLetters().stream().findFirst();
+ }
+
+ public Optional getAvailableDriveLetterPath() {
+ return getAvailableDriveLetter().map(this::toPath);
+ }
+
+ public Path toPath(String driveLetter) {
+ return Path.of(driveLetter + ":\\");
+ }
}
diff --git a/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java b/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java
index cd8514397..3d3464191 100644
--- a/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java
@@ -37,7 +37,7 @@ class EnvironmentTest {
List result = env.getSettingsPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
- MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
+ MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
Paths.get("/home/testuser/.Cryptomator/settings.json")));
}
@@ -48,7 +48,7 @@ class EnvironmentTest {
List result = env.getIpcPortPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
- MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
+ MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
}
@@ -123,8 +123,8 @@ class EnvironmentTest {
List result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(3));
- MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
- Paths.get("/home/testuser/test2"),
+ MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
+ Paths.get("/home/testuser/test2"), //
Paths.get("/foo/bar/test")));
}
diff --git a/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java
index 5ae8f0fb9..6bb4befb4 100644
--- a/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java
@@ -1,7 +1,6 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
-import org.cryptomator.common.LicenseChecker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,7 +13,7 @@ class LicenseCheckerTest {
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
+ "Al8G7CqwoJOsW7Kddns=";
-
+
private LicenseChecker licenseChecker;
@BeforeEach
@@ -25,9 +24,9 @@ class LicenseCheckerTest {
@Test
public void testCheckValidLicense() {
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
-
+
Optional decoded = licenseChecker.check(license);
-
+
Assertions.assertTrue(decoded.isPresent());
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
}
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java b/main/commons/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java
similarity index 75%
rename from main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
rename to main/commons/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java
index 67f10e035..e82e67e2d 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java
@@ -1,27 +1,30 @@
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
-import javafx.application.Platform;
-import javafx.beans.property.ReadOnlyBooleanProperty;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-class KeychainManagerTest {
-
+public class KeychainManagerTest {
+
@Test
public void testStoreAndLoad() throws KeychainAccessException {
- KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
+ KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
keychainManager.storePassphrase("test", "asd");
Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
}
-
+
@Nested
public static class WhenObservingProperties {
@@ -31,15 +34,15 @@ class KeychainManagerTest {
Platform.startup(latch::countDown);
latch.await(5, TimeUnit.SECONDS);
}
-
+
@Test
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
- KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
+ KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
Assertions.assertEquals(false, property.get());
-
+
keychainManager.storePassphrase("test", "bar");
-
+
AtomicBoolean result = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
@@ -49,7 +52,7 @@ class KeychainManagerTest {
latch.await(1, TimeUnit.SECONDS);
Assertions.assertEquals(true, result.get());
}
-
+
}
}
\ No newline at end of file
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java b/main/commons/src/test/java/org/cryptomator/common/keychain/MapKeychainAccess.java
similarity index 86%
rename from main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java
rename to main/commons/src/test/java/org/cryptomator/common/keychain/MapKeychainAccess.java
index 26b301377..c571ad716 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java
+++ b/main/commons/src/test/java/org/cryptomator/common/keychain/MapKeychainAccess.java
@@ -3,12 +3,14 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
+
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import java.util.HashMap;
import java.util.Map;
-class MapKeychainAccess implements KeychainAccessStrategy {
+class MapKeychainAccess implements KeychainAccessProvider {
private final Map map = new HashMap<>();
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 347ec1e45..cee725332 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
@@ -37,10 +37,10 @@ public class SettingsJsonAdapterTest {
}
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
- @ValueSource(strings = {
- "",
- "",
- "{invalidjson}"
+ @ValueSource(strings = { //
+ "", //
+ "", //
+ "{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
Assertions.assertThrows(IOException.class, () -> {
diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java
index 708578763..33d384cf8 100644
--- a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java
@@ -29,7 +29,7 @@ public class SettingsTest {
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
// third change (to property of list item):
- vaultSettings.mountName().set("asd");
+ vaultSettings.displayName().set("asd");
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
}
diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
index 237b00904..ff33605af 100644
--- a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
@@ -23,13 +23,13 @@ public class VaultSettingsJsonAdapterTest {
@Test
public void testDeserialize() throws IOException {
- String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
+ String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
VaultSettings vaultSettings = adapter.read(jsonReader);
Assertions.assertEquals("foo", vaultSettings.getId());
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
- Assertions.assertEquals("test", vaultSettings.mountName().get());
+ Assertions.assertEquals("test", vaultSettings.displayName().get());
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
@@ -41,7 +41,7 @@ public class VaultSettingsJsonAdapterTest {
public void testSerialize() throws IOException {
VaultSettings vaultSettings = new VaultSettings("test");
vaultSettings.path().set(Paths.get("/foo/bar"));
- vaultSettings.mountName().set("mountyMcMountFace");
+ vaultSettings.displayName().set("mountyMcMountFace");
vaultSettings.mountFlags().set("--foo --bar");
StringWriter buf = new StringWriter();
@@ -50,12 +50,12 @@ public class VaultSettingsJsonAdapterTest {
String result = buf.toString();
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
- if(System.getProperty("os.name").contains("Windows")){
+ if (System.getProperty("os.name").contains("Windows")) {
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
} else {
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
}
- MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\""));
+ MatcherAssert.assertThat(result, CoreMatchers.containsString("\"displayName\":\"mountyMcMountFace\""));
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
}
diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
index 042ff9896..8ec6dc681 100644
--- a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
@@ -8,19 +8,19 @@
*******************************************************************************/
package org.cryptomator.common.settings;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VaultSettingsTest {
- @Test
- public void testNormalize() throws Exception {
- assertEquals("_", VaultSettings.normalizeMountName(" "));
- assertEquals("a", VaultSettings.normalizeMountName("ä"));
- assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
- assertEquals("_", VaultSettings.normalizeMountName(":"));
- assertEquals("_", VaultSettings.normalizeMountName("汉语"));
+ @ParameterizedTest
+ @CsvSource({"a a,a_a", "ä,a", "Ĉ,C", ":,_", "汉语,_"})
+ public void testNormalize(String test, String expected) {
+ VaultSettings settings = new VaultSettings("id");
+ settings.displayName().setValue(test);
+ assertEquals(expected, settings.normalizeDisplayName());
}
}
diff --git a/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
index 1eb9a9d2f..907e88623 100644
--- a/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
@@ -1,9 +1,5 @@
package org.cryptomator.common.vaults;
-import javafx.beans.binding.StringBinding;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.property.SimpleStringProperty;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
@@ -17,6 +13,10 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import java.nio.file.Path;
public class VaultModuleTest {
@@ -28,7 +28,7 @@ public class VaultModuleTest {
@BeforeEach
public void setup(@TempDir Path tmpDir) {
- Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST"));
+ Mockito.when(vaultSettings.mountName()).thenReturn(Bindings.createStringBinding(() -> "TEST"));
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
System.setProperty("user.home", tmpDir.toString());
}
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
deleted file mode 100644
index fd2b26c3a..000000000
--- a/main/keychain/pom.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
- 4.0.0
-
- org.cryptomator
- main
- 1.6.0-SNAPSHOT
-
- keychain
- System Keychain Access
-
-
-
- org.cryptomator
- commons
-
-
-
-
- org.openjfx
- javafx-base
-
-
- org.openjfx
- javafx-graphics
-
-
-
-
- org.apache.commons
- commons-lang3
-
-
-
-
- com.google.code.gson
- gson
-
-
- com.google.guava
- guava
-
-
-
-
- com.google.dagger
- dagger
-
-
-
-
- de.swiesend
- secret-service
-
-
-
-
- org.slf4j
- slf4j-simple
- test
-
-
-
\ No newline at end of file
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
deleted file mode 100644
index 430b17b29..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.cryptomator.keychain;
-
-/**
- * Indicates an error during communication with the operating system's keychain.
- */
-public class KeychainAccessException extends Exception {
-
- KeychainAccessException(Throwable cause) {
- super(cause);
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
deleted file mode 100644
index abd50287e..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
+++ /dev/null
@@ -1,46 +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.keychain;
-
-interface KeychainAccessStrategy {
-
- /**
- * Associates a passphrase with a given key.
- *
- * @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
- * @param passphrase The secret to store in this keychain.
- */
- void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
-
- /**
- * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
- * @return The stored passphrase for the given key or null if no value for the given key could be found.
- */
- char[] loadPassphrase(String key) throws KeychainAccessException;
-
- /**
- * Deletes a passphrase with a given key.
- *
- * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
- */
- void deletePassphrase(String key) throws KeychainAccessException;
-
- /**
- * Updates a passphrase with a given key. Noop, if there is no item for the given key.
- *
- * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
- * @param passphrase The secret to be updated in this keychain.
- */
- void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
-
- /**
- * @return true if this KeychainAccessStrategy works on the current machine.
- * @implNote This method must not throw any exceptions and should fail fast
- * returning false if it can't determine availability of the checked strategy
- */
- boolean isSupported();
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
deleted file mode 100644
index 1db94fec6..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
+++ /dev/null
@@ -1,45 +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.keychain;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.IntoSet;
-import org.cryptomator.common.JniModule;
-
-import javax.inject.Singleton;
-import java.util.Optional;
-import java.util.Set;
-
-@Module(includes = {JniModule.class})
-public abstract class KeychainModule {
-
- @Binds
- @IntoSet
- abstract KeychainAccessStrategy bindMacSystemKeychainAccess(MacSystemKeychainAccess keychainAccessStrategy);
-
- @Binds
- @IntoSet
- abstract KeychainAccessStrategy bindWindowsProtectedKeychainAccess(WindowsProtectedKeychainAccess keychainAccessStrategy);
-
- @Binds
- @IntoSet
- abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy);
-
- @Provides
- @Singleton
- static Optional provideSupportedKeychain(Set keychainAccessStrategies) {
- return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
- }
-
- @Provides
- @Singleton
- public static Optional provideKeychainManager(Optional keychainAccess) {
- return keychainAccess.map(KeychainManager::new);
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java
deleted file mode 100644
index f11bdbd45..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.cryptomator.keychain;
-
-import org.apache.commons.lang3.SystemUtils;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.Optional;
-
-/**
- * A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
- */
-@Singleton
-public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
-
- // the actual implementation is hidden in this delegate object which is loaded via reflection,
- // as it depends on libraries that aren't necessarily available:
- private final Optional delegate;
-
- @Inject
- public LinuxSecretServiceKeychainAccess() {
- this.delegate = constructGnomeKeyringKeychainAccess();
- }
-
- private static Optional constructGnomeKeyringKeychainAccess() {
- try {
- Class> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
- KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
- return Optional.of(instance);
- } catch (Exception e) {
- return Optional.empty();
- }
- }
-
- @Override
- public boolean isSupported() {
- return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
- }
-
- @Override
- public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
- }
-
- @Override
- public char[] loadPassphrase(String key) throws KeychainAccessException {
- return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
- }
-
- @Override
- public void deletePassphrase(String key) throws KeychainAccessException {
- delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
- }
-
- @Override
- public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- delegate.orElseThrow(IllegalStateException::new).changePassphrase(key, passphrase);
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
deleted file mode 100644
index 675992e5c..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.cryptomator.keychain;
-
-import org.freedesktop.secret.simple.SimpleCollection;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
-
- private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
-
- @Override
- public boolean isSupported() {
- try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
- // seems like we're able to access the keyring.
- return true;
- } catch (IOException | RuntimeException e) {
- return false;
- }
- }
-
- @Override
- public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- try (SimpleCollection keyring = new SimpleCollection()) {
- List list = keyring.getItems(createAttributes(key));
- if (list == null) {
- keyring.createItem(LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
- }
- } catch (IOException e) {
- throw new KeychainAccessException(e);
- }
- }
-
- @Override
- public char[] loadPassphrase(String key) throws KeychainAccessException {
- try (SimpleCollection keyring = new SimpleCollection()) {
- List list = keyring.getItems(createAttributes(key));
- if (list != null) {
- return keyring.getSecret(list.get(0));
- } else {
- return null;
- }
- } catch (IOException e) {
- throw new KeychainAccessException(e);
- }
- }
-
- @Override
- public void deletePassphrase(String key) throws KeychainAccessException {
- try (SimpleCollection keyring = new SimpleCollection()) {
- List list = keyring.getItems(createAttributes(key));
- if (list != null) {
- keyring.deleteItem(list.get(0));
- }
- } catch (IOException e) {
- throw new KeychainAccessException(e);
- }
- }
-
- @Override
- public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- try (SimpleCollection keyring = new SimpleCollection()) {
- List list = keyring.getItems(createAttributes(key));
- if (list != null) {
- keyring.updateItem(list.get(0), LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
- }
- } catch (IOException e) {
- throw new KeychainAccessException(e);
- }
- }
-
- private Map createAttributes(String key) {
- Map attributes = new HashMap();
- attributes.put("Vault", key);
- return attributes;
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
deleted file mode 100644
index 0021cc13d..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
+++ /dev/null
@@ -1,58 +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.keychain;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.jni.MacFunctions;
-import org.cryptomator.jni.MacKeychainAccess;
-
-@Singleton
-class MacSystemKeychainAccess implements KeychainAccessStrategy {
-
- private final Optional macFunctions;
-
- @Inject
- public MacSystemKeychainAccess(Optional macFunctions) {
- this.macFunctions = macFunctions;
- }
-
- private MacKeychainAccess keychain() {
- return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
- }
-
- @Override
- public void storePassphrase(String key, CharSequence passphrase) {
- keychain().storePassword(key, passphrase);
- }
-
- @Override
- public char[] loadPassphrase(String key) {
- return keychain().loadPassword(key);
- }
-
- @Override
- public boolean isSupported() {
- return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
- }
-
- @Override
- public void deletePassphrase(String key) {
- keychain().deletePassword(key);
- }
-
- @Override
- public void changePassphrase(String key, CharSequence passphrase) {
- if (keychain().deletePassword(key)) {
- keychain().storePassword(key, passphrase);
- }
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
deleted file mode 100644
index 6668104a4..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
+++ /dev/null
@@ -1,209 +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.keychain;
-
-import com.google.common.io.BaseEncoding;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.google.gson.annotations.SerializedName;
-import com.google.gson.reflect.TypeToken;
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.Environment;
-import org.cryptomator.jni.WinDataProtection;
-import org.cryptomator.jni.WinFunctions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.UncheckedIOException;
-import java.io.Writer;
-import java.lang.reflect.Type;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-@Singleton
-class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
-
- private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting() //
- .registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
- .disableHtmlEscaping().create();
-
- private final Optional winFunctions;
- private final List keychainPaths;
- private Map keychainEntries;
-
- @Inject
- public WindowsProtectedKeychainAccess(Optional winFunctions, Environment environment) {
- this.winFunctions = winFunctions;
- this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
- }
-
- private WinDataProtection dataProtection() {
- return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
- }
-
- @Override
- public void storePassphrase(String key, CharSequence passphrase) {
- loadKeychainEntriesIfNeeded();
- ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
- byte[] cleartext = new byte[buf.remaining()];
- buf.get(cleartext);
- KeychainEntry entry = new KeychainEntry();
- entry.salt = generateSalt();
- entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
- Arrays.fill(buf.array(), (byte) 0x00);
- Arrays.fill(cleartext, (byte) 0x00);
- keychainEntries.put(key, entry);
- saveKeychainEntries();
- }
-
- @Override
- public char[] loadPassphrase(String key) {
- loadKeychainEntriesIfNeeded();
- KeychainEntry entry = keychainEntries.get(key);
- if (entry == null) {
- return null;
- }
- byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
- if (cleartext == null) {
- return null;
- }
- CharBuffer buf = UTF_8.decode(ByteBuffer.wrap(cleartext));
- char[] passphrase = new char[buf.remaining()];
- buf.get(passphrase);
- Arrays.fill(cleartext, (byte) 0x00);
- Arrays.fill(buf.array(), (char) 0x00);
- return passphrase;
- }
-
- @Override
- public void deletePassphrase(String key) {
- loadKeychainEntriesIfNeeded();
- keychainEntries.remove(key);
- saveKeychainEntries();
- }
-
- @Override
- public void changePassphrase(String key, CharSequence passphrase) {
- loadKeychainEntriesIfNeeded();
- if (keychainEntries.remove(key) != null) {
- storePassphrase(key, passphrase);
- }
- }
-
- @Override
- public boolean isSupported() {
- return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
- }
-
- private byte[] generateSalt() {
- byte[] result = new byte[2 * Long.BYTES];
- UUID uuid = UUID.randomUUID();
- ByteBuffer buf = ByteBuffer.wrap(result);
- buf.putLong(uuid.getMostSignificantBits());
- buf.putLong(uuid.getLeastSignificantBits());
- return result;
- }
-
- private void loadKeychainEntriesIfNeeded() {
- if (keychainEntries == null) {
- for (Path keychainPath : keychainPaths) {
- Optional