diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a0d7519cd..b351fb381 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [overheadhunter] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [overheadhunter, tobihagemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/.idea/compiler.xml b/.idea/compiler.xml index da1897030..cfda7c67b 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -7,26 +7,34 @@ - - + + - - - - - + + + + + + + - - + + + + + + + + - - + + diff --git a/.travis.yml b/.travis.yml index d0ae3c3c7..b4dd8c8cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: xenial +dist: bionic language: java sudo: false jdk: @@ -17,24 +17,25 @@ addons: - haveged install: - curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml -- mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 +- mvn -fmain/pom.xml clean install -DskipTests -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 +before_script: +- | + if [[ -n "$TRAVIS_TAG" ]]; then + mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG + else + mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7) + fi script: - mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage after_success: -- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.2/codacy-coverage-reporter-4.0.2-assembly.jar +- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/6.0.7/codacy-coverage-reporter-6.0.7-assembly.jar - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial - $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final before_deploy: -- | - if [[ -n "$TRAVIS_TAG" ]]; then - mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG - elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then - mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7) - fi -- mvn -fmain/pom.xml clean package -Prelease -DskipTests +- mvn -fmain/pom.xml package -Prelease -DskipTests - export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json deploy: - provider: bintray # SNAPSHOTS diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml index 9e7251134..11ad987bd 100644 --- a/main/buildkit/pom.xml +++ b/main/buildkit/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 buildkit pom diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 5d7526970..1f9b121ec 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 commons Cryptomator Commons diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index 3960cf1f6..ff5613f1e 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 @@ -17,6 +17,7 @@ import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.geometry.NodeOrientation; import java.util.function.Consumer; @@ -30,9 +31,10 @@ public class Settings { public static final int DEFAULT_PORT = 42427; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV; - public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT; 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 UiTheme DEFAULT_THEME = UiTheme.LIGHT; + public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT; private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK); @@ -44,6 +46,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 userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION); private Consumer saveCmd; @@ -61,6 +64,7 @@ public class Settings { debugMode.addListener(this::somethingChanged); preferredVolumeImpl.addListener(this::somethingChanged); theme.addListener(this::somethingChanged); + userInterfaceOrientation.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { @@ -118,4 +122,8 @@ public class Settings { public ObjectProperty theme() { return theme; } + + 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 0929ed050..874994cf8 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,6 +9,7 @@ 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; @@ -36,6 +37,7 @@ public class SettingsJsonAdapter extends TypeAdapter { out.name("debugMode").value(value.debugMode().get()); 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.endObject(); } @@ -85,6 +87,9 @@ public class SettingsJsonAdapter extends TypeAdapter { case "theme": settings.theme().set(parseUiTheme(in.nextString())); break; + case "uiOrientation": + settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString())); + break; default: LOG.warn("Unsupported vault setting found in JSON: " + name); in.skipValue(); @@ -109,7 +114,7 @@ public class SettingsJsonAdapter extends TypeAdapter { try { return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase()); } catch (IllegalArgumentException e) { - LOG.warn("Invalid volume type {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME); + LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME); return Settings.DEFAULT_GVFS_SCHEME; } } @@ -118,11 +123,20 @@ public class SettingsJsonAdapter extends TypeAdapter { try { return UiTheme.valueOf(uiThemeName.toUpperCase()); } catch (IllegalArgumentException e) { - LOG.warn("Invalid volume type {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME); + LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME); return Settings.DEFAULT_THEME; } } + private NodeOrientation parseUiOrientation(String uiOrientationName) { + try { + return NodeOrientation.valueOf(uiOrientationName.toUpperCase()); + } catch (IllegalArgumentException e) { + LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION); + return Settings.DEFAULT_USER_INTERFACE_ORIENTATION; + } + } + private List readVaultSettingsArray(JsonReader in) throws IOException { List result = new ArrayList<>(); in.beginArray(); 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 9ffcdbc6c..4b54076bd 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 @@ -69,8 +69,9 @@ public class DokanyVolume implements Volume { } else { //auto assign drive letter if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) { - return windowsDriveLetters.getAvailableDriveLetters().iterator().next(); + return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\"); } else { + //TODO: Error Handling throw new VolumeException("No free drive letter available."); } } 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 5d644ee7b..6f1edfec2 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.Sets; import org.apache.commons.lang3.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +15,6 @@ import javax.inject.Singleton; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; @@ -23,11 +23,11 @@ import java.util.stream.StreamSupport; public final class WindowsDriveLetters { private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class); - private static final Set D_TO_Z; + private static final Set A_TO_Z; static { - try (IntStream stream = IntStream.rangeClosed('D', 'Z')) { - D_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet()); + try (IntStream stream = IntStream.rangeClosed('A', 'Z')) { + A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet()); } } @@ -35,20 +35,22 @@ public final class WindowsDriveLetters { public WindowsDriveLetters() { } - public Set getOccupiedDriveLetters() { + public Set getAllDriveLetters() { + return A_TO_Z; + } + + public Set getOccupiedDriveLetters() { if (!SystemUtils.IS_OS_WINDOWS) { LOG.warn("Attempted to get occupied drive letters on non-Windows machine."); return Set.of(); } else { Iterable rootDirs = FileSystems.getDefault().getRootDirectories(); - return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toSet()); + return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0,1)).collect(Collectors.toSet()); } } - public Set getAvailableDriveLetters() { - Set occupiedDriveLetters = getOccupiedDriveLetters(); - Predicate isOccupiedDriveLetter = occupiedDriveLetters::contains; - return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet()); + public Set getAvailableDriveLetters() { + return Sets.difference(A_TO_Z, getOccupiedDriveLetters()); } } diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index 0bcdce324..441e75af8 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 keychain System Keychain Access diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index 247e4eb47..a56cd33c6 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 launcher Cryptomator Launcher diff --git a/main/pom.xml b/main/pom.xml index 87522602c..026092229 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 pom Cryptomator @@ -24,29 +24,28 @@ UTF-8 - 1.9.0-beta4 + 1.9.0-rc2 2.2.1 - 1.2.0 + 1.2.1 1.1.11 1.0.10 - 13 + 13.0.1 - 2.6 - 3.8.1 + 3.9 1.0.3 - 27.1-jre - 2.22.1 - 2.8.5 + 28.1-jre + 2.25.2 + 2.8.6 - 1.7.26 + 1.7.29 1.2.3 - 5.4.2 - 2.27.0 - 2.1 + 5.5.2 + 3.1.0 + 2.2 @@ -152,11 +151,6 @@ - - commons-io - commons-io - ${commons-io.version} - org.apache.commons commons-lang3 @@ -267,7 +261,7 @@ maven-dependency-plugin - 3.1.0 + 3.1.1 copy-libs @@ -296,7 +290,7 @@ org.jacoco jacoco-maven-plugin - 0.8.2 + 0.8.5 prepare-agent @@ -323,7 +317,7 @@ maven-compiler-plugin - 3.8.0 + 3.8.1 11 @@ -338,7 +332,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.1 + 2.22.2 diff --git a/main/ui/pom.xml b/main/ui/pom.xml index c171a9794..e2c17e375 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha2 + 1.5.0-beta1 ui Cryptomator GUI @@ -50,10 +50,6 @@ - - commons-io - commons-io - org.apache.commons commons-lang3 diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java b/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java index 14efe8f27..a2757d7b7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java @@ -8,6 +8,7 @@ import javafx.scene.input.KeyCombination; import javafx.stage.Stage; import javafx.stage.Window; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.fxapp.FxApplicationScoped; import javax.inject.Inject; @@ -19,16 +20,25 @@ public class DefaultSceneFactory implements Function { protected static final KeyCodeCombination ALT_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN); protected static final KeyCodeCombination SHORTCUT_W = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); + protected final Settings settings; + @Inject - public DefaultSceneFactory() {} + public DefaultSceneFactory(Settings settings) { + this.settings = settings; + } @Override public Scene apply(Parent root) { Scene scene = new Scene(root); + configureRoot(root); configureScene(scene); return scene; } + protected void configureRoot(Parent root) { + root.nodeOrientationProperty().bind(settings.userInterfaceOrientation()); + } + protected void configureScene(Scene scene) { scene.windowProperty().addListener(observable -> { Window window = scene.getWindow(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java index 8c6486dc6..b86a56e5f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java @@ -7,6 +7,7 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.stage.Stage; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.DefaultSceneFactory; import javax.inject.Inject; @@ -20,7 +21,8 @@ public class MainWindowSceneFactory extends DefaultSceneFactory { private final Lazy vaultListController; @Inject - public MainWindowSceneFactory(Lazy mainWindowController, Lazy vaultListController) { + public MainWindowSceneFactory(Settings settings, Lazy mainWindowController, Lazy vaultListController) { + super(settings); this.mainWindowController = mainWindowController; this.vaultListController = vaultListController; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index d03832358..bbccb0ad7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -1,21 +1,33 @@ package org.cryptomator.ui.preferences; +import javafx.beans.value.ObservableValue; +import javafx.geometry.NodeOrientation; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; import javafx.util.StringConverter; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.UiTheme; import org.cryptomator.ui.common.FxController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; @PreferencesScoped public class GeneralPreferencesController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class); private final Settings settings; public ChoiceBox themeChoiceBox; public CheckBox startHiddenCheckbox; public CheckBox debugModeCheckbox; + public ToggleGroup nodeOrientation; + public RadioButton nodeOrientationLtr; + public RadioButton nodeOrientationRtl; @Inject GeneralPreferencesController(Settings settings) { @@ -30,6 +42,20 @@ public class GeneralPreferencesController implements FxController { startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); + + nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); + nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); + nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); + } + + private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (nodeOrientationLtr.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); + } else if (nodeOrientationRtl.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); + } else { + LOG.warn("Unexpected toggle option {}", newValue); + } } /* Helper classes */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java index 691035bd3..9dd1bef82 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java @@ -16,6 +16,9 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; +/** + * TODO: if WebDAV is selected under Windows, show warning that specific mount options (like selecting a directory as mount point) are _not_ supported + */ @PreferencesScoped public class VolumePreferencesController implements FxController { diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 4ebb35832..6efa3e407 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -1,34 +1,76 @@ package org.cryptomator.ui.vaultoptions; +import com.google.common.base.Strings; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import javafx.util.StringConverter; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.WindowsDriveLetters; import org.cryptomator.ui.common.FxController; import javax.inject.Inject; +import java.io.File; +import java.nio.file.Path; +import java.util.ResourceBundle; +import java.util.Set; +/** + * TODO: if WebDav is selected on a windows system, custom mount directory is _not_ supported. This is currently not indicated/shown/etc in the ui + */ @VaultOptionsScoped public class MountOptionsController implements FxController { + private final Stage window; private final Vault vault; + private final BooleanProperty osIsWindows = new SimpleBooleanProperty(SystemUtils.IS_OS_WINDOWS); + private final BooleanBinding webDavAndWindows; + private final WindowsDriveLetters windowsDriveLetters; + private final ResourceBundle resourceBundle; public TextField driveName; public CheckBox readOnlyCheckbox; public CheckBox customMountFlagsCheckbox; public TextField mountFlags; + public ToggleGroup mountPoint; + public RadioButton mountPointAuto; + public RadioButton mountPointWinDriveLetter; + public RadioButton mountPointCustomDir; + public ChoiceBox driveLetterSelection; @Inject - MountOptionsController(@VaultOptionsWindow Vault vault) { + MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle) { + this.window = window; this.vault = vault; + this.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows); + this.windowsDriveLetters = windowsDriveLetters; + this.resourceBundle = resourceBundle; } @FXML public void initialize() { driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName()); + + // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); - mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty()); + // custom mount flags: + mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags()); if (vault.isHavingCustomMountFlags()) { mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags()); @@ -36,6 +78,25 @@ public class MountOptionsController implements FxController { } else { mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); } + + // mount point options: + mountPoint.selectedToggleProperty().addListener(this::toggleMountPoint); + driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters()); + driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters)); + driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get()); + vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir)); + vault.getVaultSettings().winDriveLetter().bind( // + Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) // + .then(driveLetterSelection.getSelectionModel().selectedItemProperty()) // + .otherwise((String) null) // + ); + if (vault.getVaultSettings().usesIndividualMountPath().get()) { + mountPoint.selectToggle(mountPointCustomDir); + } else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) { + mountPoint.selectToggle(mountPointWinDriveLetter); + } else { + mountPoint.selectToggle(mountPointAuto); + } } @FXML @@ -51,4 +112,81 @@ public class MountOptionsController implements FxController { mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); } } + + @FXML + private void chooseCustomMountPoint() { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.winDirChooser")); + try { + directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile()); + } catch (Exception e) { + //NO-OP + } + File file = directoryChooser.showDialog(window); + if (file != null) { + vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath()); + } else { + vault.getVaultSettings().individualMountPath().set(null); + } + } + + private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().individualMountPath().get())) { + chooseCustomMountPoint(); + } + } + + /** + * Converts 'C' to "C:" to translate between model and GUI. + */ + private static class WinDriveLetterLabelConverter extends StringConverter { + + private final Set occupiedDriveLetters; + + WinDriveLetterLabelConverter(WindowsDriveLetters windowsDriveLetters) { + this.occupiedDriveLetters = windowsDriveLetters.getOccupiedDriveLetters(); + } + + @Override + public String toString(String driveLetter) { + if (occupiedDriveLetters.contains(driveLetter)) { + return driveLetter + ": (occupied)"; // TODO localize? + } else { + return driveLetter + ":"; + } + } + + @Override + public String fromString(String string) { + throw new UnsupportedOperationException(); + } + + } + + // Getter & Setter + + public BooleanProperty osIsWindowsProperty() { + return osIsWindows; + } + + public boolean getOsIsWindows() { + return osIsWindows.get(); + } + + public BooleanBinding webDavAndWindowsProperty() { + return webDavAndWindows; + } + + public boolean isWebDavAndWindows() { + return webDavAndWindows.get(); + } + + public StringProperty customMountPathProperty() { + return vault.getVaultSettings().individualMountPath(); + } + + public String getCustomMountPath() { + return vault.getVaultSettings().individualMountPath().get(); + } + } diff --git a/main/ui/src/main/resources/fxml/preferences_general.fxml b/main/ui/src/main/resources/fxml/preferences_general.fxml index 3546ebe65..1f05151f8 100644 --- a/main/ui/src/main/resources/fxml/preferences_general.fxml +++ b/main/ui/src/main/resources/fxml/preferences_general.fxml @@ -6,10 +6,15 @@ + + + + + @@ -18,6 +23,12 @@