From 17f45c6dab12aa0abb594617c293feb5e093d93f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 7 Aug 2019 17:04:56 +0200 Subject: [PATCH] Implemented some mount options (references #931) --- .../org/cryptomator/common/vaults/Vault.java | 22 ++- .../common/vaults/VaultModule.java | 148 ++++++++++-------- .../ui/controllers/UnlockController.java | 12 +- .../ui/controls/AlphanumericTextField.java | 20 +++ .../vaultoptions/MountOptionsController.java | 44 +++++- .../resources/fxml/vault_options_mount.fxml | 16 +- .../main/resources/i18n/strings.properties | 5 +- .../main/resources/i18n/strings_en.properties | 5 +- 8 files changed, 184 insertions(+), 88 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/controls/AlphanumericTextField.java 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 9d6a4e145..7d0ef2124 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 @@ -15,7 +15,6 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; @@ -45,7 +44,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; -import java.util.function.Supplier; @PerVault public class Vault { @@ -57,7 +55,7 @@ public class Vault { private final VaultSettings vaultSettings; private final Provider volumeProvider; - private final Supplier defaultMountFlags; + private final StringBinding defaultMountFlags; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); private final StringBinding displayableName; @@ -72,7 +70,7 @@ public class Vault { } @Inject - Vault(VaultSettings vaultSettings, Provider volumeProvider, @DefaultMountFlags Supplier defaultMountFlags) { + Vault(VaultSettings vaultSettings, Provider volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags) { this.vaultSettings = vaultSettings; this.volumeProvider = volumeProvider; this.defaultMountFlags = defaultMountFlags; @@ -122,7 +120,7 @@ public class Vault { } CryptoFileSystem fs = getCryptoFileSystem(passphrase); volume = volumeProvider.get(); - volume.mount(fs, getMountFlags()); + volume.mount(fs, getEffectiveMountFlags()); } public synchronized void lock(boolean forced) throws Volume.VolumeException { @@ -318,16 +316,24 @@ public class Vault { return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get()); } - public String getMountFlags() { + public StringBinding defaultMountFlagsProperty() { + return defaultMountFlags; + } + + public String getDefaultMountFlags() { + return defaultMountFlags.get(); + } + + public String getEffectiveMountFlags() { String mountFlags = vaultSettings.mountFlags().get(); if (Strings.isNullOrEmpty(mountFlags)) { - return defaultMountFlags.get(); + return getDefaultMountFlags(); } else { return mountFlags; } } - public void setMountFlags(String mountFlags) { + public void setCustomMountFlags(String mountFlags) { vaultSettings.mountFlags().set(mountFlags); } 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 b3d7a4497..6258552da 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,6 +7,13 @@ package org.cryptomator.common.vaults; import dagger.Module; import dagger.Provides; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.StringProperty; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; @@ -44,90 +51,95 @@ public class VaultModule { @Provides @PerVault @DefaultMountFlags - public Supplier provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { - return () -> { - VolumeImpl preferredImpl = settings.preferredVolumeImpl().get(); - switch (preferredImpl) { - case FUSE: - if (SystemUtils.IS_OS_MAC_OSX) { - return getMacFuseDefaultMountFlags(settings, vaultSettings); - } else if (SystemUtils.IS_OS_LINUX) { - return getLinuxFuseDefaultMountFlags(settings, vaultSettings); - } - case DOKANY: - return getDokanyDefaultMountFlags(settings, vaultSettings); - default: - return "--flags-supported-on-FUSE-or-DOKANY-only"; - } - }; + public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + BooleanBinding isMacFuse = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_MAC_OSX).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE)); + BooleanBinding isLinuxFuse = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_LINUX).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE)); + BooleanBinding isWinDokany = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_WINDOWS).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.DOKANY)); + + return Bindings.when(isMacFuse) // IF isMacFuse + .then(getMacFuseDefaultMountFlags(vaultSettings)) // + .otherwise(Bindings.when(isLinuxFuse) // ELSE IF isLinuxFuse + .then(getLinuxFuseDefaultMountFlags(vaultSettings)) // + .otherwise(Bindings.when(isWinDokany) // ELSE IF isWinDokany + .then(getDokanyDefaultMountFlags(vaultSettings)) // + .otherwise("--flags-supported-on-FUSE-or-DOKANY-only") // ELSE + ) // + ); } // see: https://github.com/osxfuse/osxfuse/wiki/Mount-options - private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + private StringBinding getMacFuseDefaultMountFlags(VaultSettings vaultSettings) { assert SystemUtils.IS_OS_MAC_OSX; + StringProperty mountName = vaultSettings.mountName(); + BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); + return Bindings.createStringBinding(() -> { + StringBuilder flags = new StringBuilder(); + if (readOnly.get()) { + flags.append(" -ordonly"); + } + flags.append(" -ovolname=").append(mountName.get()); + flags.append(" -oatomic_o_trunc"); + flags.append(" -oauto_xattr"); + flags.append(" -oauto_cache"); + flags.append(" -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding + flags.append(" -onoappledouble"); // vastly impacts performance for some reason... + flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc - StringBuilder flags = new StringBuilder(); - if (vaultSettings.usesReadOnlyMode().get()) { - flags.append(" -ordonly"); - } - flags.append(" -ovolname=").append(vaultSettings.mountName().get()); - flags.append(" -oatomic_o_trunc"); - flags.append(" -oauto_xattr"); - flags.append(" -oauto_cache"); - flags.append(" -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding - flags.append(" -onoappledouble"); // vastly impacts performance for some reason... - flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc + try { + Path userHome = Paths.get(System.getProperty("user.home")); + int uid = (int) Files.getAttribute(userHome, "unix:uid"); + int gid = (int) Files.getAttribute(userHome, "unix:gid"); + flags.append(" -ouid=").append(uid); + flags.append(" -ogid=").append(gid); + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + } - try { - Path userHome = Paths.get(System.getProperty("user.home")); - int uid = (int) Files.getAttribute(userHome, "unix:uid"); - int gid = (int) Files.getAttribute(userHome, "unix:gid"); - flags.append(" -ouid=").append(uid); - flags.append(" -ogid=").append(gid); - } catch (IOException e) { - LOG.error("Could not read uid/gid from USER_HOME", e); - } - - return flags.toString().strip(); + return flags.toString().strip(); + }, mountName, readOnly); } // see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html - private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + private StringBinding getLinuxFuseDefaultMountFlags(VaultSettings vaultSettings) { assert SystemUtils.IS_OS_LINUX; + BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); + return Bindings.createStringBinding(() -> { + StringBuilder flags = new StringBuilder(); + if (readOnly.get()) { + flags.append(" -oro"); + } + flags.append(" -oauto_unmount"); - StringBuilder flags = new StringBuilder(); - if (vaultSettings.usesReadOnlyMode().get()) { - flags.append(" -oro"); - } - flags.append(" -oauto_unmount"); + try { + Path userHome = Paths.get(System.getProperty("user.home")); + int uid = (int) Files.getAttribute(userHome, "unix:uid"); + int gid = (int) Files.getAttribute(userHome, "unix:gid"); + flags.append(" -ouid=").append(uid); + flags.append(" -ogid=").append(gid); + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + } - try { - Path userHome = Paths.get(System.getProperty("user.home")); - int uid = (int) Files.getAttribute(userHome, "unix:uid"); - int gid = (int) Files.getAttribute(userHome, "unix:gid"); - flags.append(" -ouid=").append(uid); - flags.append(" -ogid=").append(gid); - } catch (IOException e) { - LOG.error("Could not read uid/gid from USER_HOME", e); - } - - return flags.toString().strip(); + return flags.toString().strip(); + }, readOnly); } // see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34 - private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + private StringBinding getDokanyDefaultMountFlags(VaultSettings vaultSettings) { assert SystemUtils.IS_OS_WINDOWS; - - StringBuilder flags = new StringBuilder(); - flags.append(" --options CURRENT_SESSION"); - if (vaultSettings.usesReadOnlyMode().get()) { - flags.append(",WRITE_PROTECTION"); - } - flags.append(" --thread-count 5"); - flags.append(" --timeout 10000"); - flags.append(" --allocation-unit-size 4096"); - flags.append(" --sector-size 4096"); - return flags.toString().strip(); + BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); + return Bindings.createStringBinding(() -> { + StringBuilder flags = new StringBuilder(); + flags.append(" --options CURRENT_SESSION"); + if (readOnly.get()) { + flags.append(",WRITE_PROTECTION"); + } + flags.append(" --thread-count 5"); + flags.append(" --timeout 10000"); + flags.append(" --allocation-unit-size 4096"); + flags.append(" --sector-size 4096"); + return flags.toString().strip(); + }, readOnly); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 272dd408a..7e00616a2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -211,7 +211,7 @@ public class UnlockController implements ViewController { downloadsPageLink.setManaged(false); mountName.setText(vault.getMountName()); useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags()); - mountFlags.setText(vault.getMountFlags()); + mountFlags.setText(vault.getEffectiveMountFlags()); savePassword.setSelected(false); // auto-fill pw from keychain: if (keychainAccess.isPresent()) { @@ -321,28 +321,28 @@ public class UnlockController implements ViewController { vault.setMountName(newValue); } if (!useCustomMountFlags.isSelected()) { - mountFlags.setText(vault.getMountFlags()); // update default flags + mountFlags.setText(vault.getEffectiveMountFlags()); // update default flags } } private void useReadOnlyDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) { vault.getVaultSettings().usesReadOnlyMode().setValue(newValue); if (!useCustomMountFlags.isSelected()) { - mountFlags.setText(vault.getMountFlags()); // update default flags + mountFlags.setText(vault.getEffectiveMountFlags()); // update default flags } } private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) { if (!newValue) { - vault.setMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS); - mountFlags.setText(vault.getMountFlags()); + vault.setCustomMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS); + mountFlags.setText(vault.getEffectiveMountFlags()); } } private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused") String oldValue, String newValue) { if (useCustomMountFlags.isSelected()) { - vault.setMountFlags(newValue); + vault.setCustomMountFlags(newValue); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/AlphanumericTextField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/AlphanumericTextField.java new file mode 100644 index 000000000..9271ca088 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/AlphanumericTextField.java @@ -0,0 +1,20 @@ +package org.cryptomator.ui.controls; + +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; + +import java.util.regex.Pattern; + +public class AlphanumericTextField extends TextField { + + private final static Pattern DIGIT_PATTERN = Pattern.compile("\\w*"); + + public AlphanumericTextField() { + this.setTextFormatter(new TextFormatter<>(this::filterNumericTextChange)); + } + + private TextFormatter.Change filterNumericTextChange(TextFormatter.Change change) { + return DIGIT_PATTERN.matcher(change.getText()).matches() ? change : null; + } + +} 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 e5117b8cd..ac7b2b1d8 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,13 +1,51 @@ package org.cryptomator.ui.vaultoptions; +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TextField; +import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import javax.inject.Inject; @VaultOptionsScoped public class MountOptionsController implements FxController { - - @Inject - MountOptionsController(){} + private final Vault vault; + public TextField driveName; + public CheckBox readOnlyCheckbox; + public CheckBox customMountFlagsCheckbox; + public TextField mountFlags; + + @Inject + MountOptionsController(@VaultOptionsWindow Vault vault) { + this.vault = vault; + } + + @FXML + public void initialize() { + driveName.textProperty().bindBidirectional(vault.getVaultSettings().mountName()); + readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); + mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); + + customMountFlagsCheckbox.setSelected(vault.isHavingCustomMountFlags()); + if (vault.isHavingCustomMountFlags()) { + mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags()); + } else { + mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); + } + } + + @FXML + public void toggleUseCustomMountFlags() { + if (customMountFlagsCheckbox.isSelected()) { + mountFlags.textProperty().unbind(); + vault.setCustomMountFlags(vault.defaultMountFlagsProperty().get()); + mountFlags.textProperty().bindBidirectional(vault.getVaultSettings().mountFlags()); + } else { + mountFlags.textProperty().unbindBidirectional(vault.getVaultSettings().mountFlags()); + vault.setCustomMountFlags(null); + mountFlags.textProperty().bind(vault.defaultMountFlagsProperty()); + } + } } diff --git a/main/ui/src/main/resources/fxml/vault_options_mount.fxml b/main/ui/src/main/resources/fxml/vault_options_mount.fxml index 6eda6eb73..34d04b96f 100644 --- a/main/ui/src/main/resources/fxml/vault_options_mount.fxml +++ b/main/ui/src/main/resources/fxml/vault_options_mount.fxml @@ -2,7 +2,11 @@ + + + + - diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties index 62fce197d..f53be2cb2 100644 --- a/main/ui/src/main/resources/i18n/strings.properties +++ b/main/ui/src/main/resources/i18n/strings.properties @@ -16,4 +16,7 @@ preferences.volumeType=Volume type unlock.deleteSavedPasswordDialog.title=Delete Saved Password unlock.deleteSavedPasswordDialog.header=Do you really want to delete the saved password of this vault? unlock.deleteSavedPasswordDialog.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled. -vaultlist.emptyList.onboardingInstruction=Click here to add a vault \ No newline at end of file +vaultlist.emptyList.onboardingInstruction=Click here to add a vault +vaultOptions.mount.readonly=Read-Only +vaultOptions.mount.driveName=Drive Name +vaultOptions.mount.customMountFlags=Custom Mount Flags \ No newline at end of file diff --git a/main/ui/src/main/resources/i18n/strings_en.properties b/main/ui/src/main/resources/i18n/strings_en.properties index cddfadb4d..00b918515 100644 --- a/main/ui/src/main/resources/i18n/strings_en.properties +++ b/main/ui/src/main/resources/i18n/strings_en.properties @@ -12,4 +12,7 @@ preferences.debugLogging=Enable debug logging preferences.startHidden=Hide window when starting Cryptomator preferences.theme=Look & Feel preferences.volumeType=Volume type -vaultlist.emptyList.onboardingInstruction=Click here to add a vault \ No newline at end of file +vaultlist.emptyList.onboardingInstruction=Click here to add a vault +vaultOptions.mount.readonly=Read-Only +vaultOptions.mount.driveName=Drive Name +vaultOptions.mount.customMountFlags=Custom Mount Flags \ No newline at end of file