From b15d410378db19d06be0e1bfe8e9cabff0455cea Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 16:39:56 +0200 Subject: [PATCH] Re-layouted unlock UI and added textfield for custom mount flags (atm only supported for FUSE - see #802) --- .../ui/controllers/UnlockController.java | 41 +++-- .../ui/model/DefaultMountFlags.java | 13 ++ .../cryptomator/ui/model/DokanyVolume.java | 2 +- .../org/cryptomator/ui/model/FuseVolume.java | 19 +-- .../org/cryptomator/ui/model/PerVault.java | 13 ++ .../java/org/cryptomator/ui/model/Vault.java | 33 +++- .../cryptomator/ui/model/VaultComponent.java | 1 - .../org/cryptomator/ui/model/VaultModule.java | 79 ++++++++-- .../java/org/cryptomator/ui/model/Volume.java | 2 +- .../cryptomator/ui/model/WebDavVolume.java | 3 +- main/ui/src/main/resources/fxml/unlock.fxml | 145 ++++++++---------- .../ui/src/main/resources/localization/en.txt | 5 +- 12 files changed, 226 insertions(+), 130 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java 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 3d90c689f..dd9808070 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 @@ -11,6 +11,7 @@ package org.cryptomator.ui.controllers; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import javafx.application.Application; +import javafx.beans.Observable; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -27,6 +28,7 @@ import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; @@ -113,6 +115,12 @@ public class UnlockController implements ViewController { @FXML private TextField mountName; + @FXML + private CheckBox useCustomMountFlags; + + @FXML + private TextField mountFlags; + @FXML private CheckBox revealAfterMount; @@ -134,17 +142,14 @@ public class UnlockController implements ViewController { @FXML private ProgressIndicator progressIndicator; - @FXML - private Text progressText; - @FXML private Hyperlink downloadsPageLink; @FXML - private GridPane advancedOptions; + private VBox advancedOptions; @FXML - private GridPane root; + private VBox root; @FXML private CheckBox unlockAfterStartup; @@ -158,6 +163,9 @@ public class UnlockController implements ViewController { unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty()); mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents); mountName.textProperty().addListener(this::mountNameDidChange); + useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange); + mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not()); + mountFlags.textProperty().addListener(this::mountFlagsDidChange); savePassword.setDisable(!keychainAccess.isPresent()); unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not())); @@ -173,7 +181,6 @@ public class UnlockController implements ViewController { } } - @Override public Parent getRoot() { return root; @@ -199,7 +206,6 @@ public class UnlockController implements ViewController { advancedOptions.setVisible(false); advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show")); progressIndicator.setVisible(false); - progressText.setText(null); state.successMessage().map(localization::getString).ifPresent(messageText::setText); if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.valueProperty().removeListener(driveLetterChangeListener); @@ -212,6 +218,8 @@ public class UnlockController implements ViewController { } downloadsPageLink.setVisible(false); mountName.setText(vault.getMountName()); + useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags()); + mountFlags.setText(vault.getMountFlags()); savePassword.setSelected(false); // auto-fill pw from keychain: if (keychainAccess.isPresent()) { @@ -318,6 +326,23 @@ public class UnlockController implements ViewController { } else { vault.setMountName(newValue); } + if (!useCustomMountFlags.isSelected()) { + mountFlags.setText(vault.getMountFlags()); // flags might depend on the volume name + } + } + + + 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()); + } + } + + private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused")String oldValue, String newValue) { + if (useCustomMountFlags.isSelected()) { + vault.setMountFlags(newValue); + } } @FXML @@ -435,7 +460,6 @@ public class UnlockController implements ViewController { CharSequence password = passwordField.getCharacters(); Tasks.create(() -> { - progressText.setText(localization.getString("unlock.pendingMessage.unlocking")); vault.unlock(password); if (keychainAccess.isPresent() && savePassword.isSelected()) { keychainAccess.get().storePassphrase(vault.getId(), password); @@ -476,7 +500,6 @@ public class UnlockController implements ViewController { }).andFinally(() -> { advancedOptions.setDisable(false); progressIndicator.setVisible(false); - progressText.setText(null); }).runOnce(executor); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java b/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java new file mode 100644 index 000000000..50943a34f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.model; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DefaultMountFlags { +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java index b9200a981..1c419830e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java @@ -44,7 +44,7 @@ public class DokanyVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws VolumeException, IOException { + public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException { Path mountPath = getMountPoint(); String mountName = vaultSettings.mountName().get(); try { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java index dbce4eccb..b740dfcbd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.model; +import com.google.common.base.Splitter; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; @@ -21,7 +22,6 @@ import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import java.util.Optional; public class FuseVolume implements Volume { @@ -44,7 +44,7 @@ public class FuseVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException { + public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException { Optional optionalCustomMountPoint = vaultSettings.getIndividualMountPath(); if (optionalCustomMountPoint.isPresent()) { Path customMountPoint = Paths.get(optionalCustomMountPoint.get()); @@ -55,7 +55,7 @@ public class FuseVolume implements Volume { this.mountPoint = prepareTemporaryMountPoint(); LOG.debug("Successfully created mount point: {}", mountPoint); } - mount(fs.getPath("/")); + mount(fs.getPath("/"), mountFlags); } private void checkProvidedMountPoint(Path mountPoint) throws IOException { @@ -96,11 +96,11 @@ public class FuseVolume implements Volume { throw new VolumeException("Did not find feasible mount point."); } - private void mount(Path root) throws VolumeException { + private void mount(Path root, String mountFlags) throws VolumeException { try { Mounter mounter = FuseMountFactory.getMounter(); EnvironmentVariables envVars = EnvironmentVariables.create() // - .withFlags(mountFlags(mounter)) + .withFlags(splitFlags(mountFlags)) .withMountPoint(mountPoint) // .build(); this.fuseMnt = mounter.mount(root, envVars); @@ -109,13 +109,8 @@ public class FuseVolume implements Volume { } } - private String[] mountFlags(Mounter mounter) { - List mountFlags = vaultSettings.mountFlags().get(); - if (mountFlags.isEmpty()) { - return mounter.defaultMountFlags(); - } else { - return mountFlags.toArray(String[]::new); - } + private String[] splitFlags(String str) { + return Splitter.on(' ').splitToList(str).toArray(String[]::new); } @Override diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java b/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java new file mode 100644 index 000000000..3172beec0 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.model; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface PerVault { + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index bd48150fc..325f6ba97 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -25,7 +25,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags; import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.ui.model.VaultModule.PerVault; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +43,7 @@ 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 { @@ -54,6 +54,7 @@ public class Vault { private final VaultSettings vaultSettings; private final Provider volumeProvider; + private final Supplier defaultMountFlags; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); @@ -64,9 +65,10 @@ public class Vault { } @Inject - Vault(VaultSettings vaultSettings, Provider volumeProvider) { + Vault(VaultSettings vaultSettings, Provider volumeProvider, @DefaultMountFlags Supplier defaultMountFlags) { this.vaultSettings = vaultSettings; this.volumeProvider = volumeProvider; + this.defaultMountFlags = defaultMountFlags; } // ****************************************************************************** @@ -110,7 +112,7 @@ public class Vault { } CryptoFileSystem fs = getCryptoFileSystem(passphrase); volume = volumeProvider.get(); - volume.mount(fs); + volume.mount(fs, getMountFlags()); Platform.runLater(() -> { state.set(State.UNLOCKED); }); @@ -241,10 +243,6 @@ public class Vault { } } - public String getMountName() { - return vaultSettings.mountName().get(); - } - public String getCustomMountPath() { return vaultSettings.individualMountPath().getValueSafe(); } @@ -253,6 +251,10 @@ public class Vault { vaultSettings.individualMountPath().set(mountPath); } + public String getMountName() { + return vaultSettings.mountName().get(); + } + public void setMountName(String mountName) throws IllegalArgumentException { if (StringUtils.isBlank(mountName)) { throw new IllegalArgumentException("mount name is empty"); @@ -261,6 +263,23 @@ public class Vault { } } + public boolean isHavingCustomMountFlags() { + return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get()); + } + + public String getMountFlags() { + String mountFlags = vaultSettings.mountFlags().get(); + if (Strings.isNullOrEmpty(mountFlags)) { + return defaultMountFlags.get(); + } else { + return mountFlags; + } + } + + public void setMountFlags(String mountFlags) { + vaultSettings.mountFlags().set(mountFlags); + } + public Character getWinDriveLetter() { if (vaultSettings.winDriveLetter().get() == null) { return null; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java index 6fafed661..bca3dab63 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java @@ -7,7 +7,6 @@ package org.cryptomator.ui.model; import dagger.BindsInstance; import org.cryptomator.common.settings.VaultSettings; -import org.cryptomator.ui.model.VaultModule.PerVault; import dagger.Subcomponent; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java index 100137f6f..8ca71d692 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java @@ -7,28 +7,24 @@ package org.cryptomator.ui.model; import dagger.Module; import dagger.Provides; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Supplier; @Module public class VaultModule { private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class); - @Scope - @Documented - @Retention(RetentionPolicy.RUNTIME) - @interface PerVault { - - } - @Provides public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) { VolumeImpl preferredImpl = settings.preferredVolumeImpl().get(); @@ -45,4 +41,65 @@ public class VaultModule { } } + @Provides + @PerVault + @DefaultMountFlags + public Supplier provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + 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"; + } + } + + private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + assert SystemUtils.IS_OS_MAC_OSX; + // see: https://github.com/osxfuse/osxfuse/wiki/Mount-options + 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"); + return "-ovolname=" + vaultSettings.mountName().get() // volume name + + " -ouid=" + uid // + + " -ogid=" + gid // + + " -oatomic_o_trunc" // + + " -oauto_xattr" // + + " -oauto_cache" // + + " -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC" // show files names in Unicode NFD encoding + + " -onoappledouble" // vastly impacts performance for some reason... + + " -odefault_permissions"; // let the kernel assume permissions based on file attributes etc + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + return ""; + } + } + + private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + assert SystemUtils.IS_OS_LINUX; + 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"); + return "-oauto_unmount" // + + " -ouid=" + uid // + + " -ogid=" + gid; + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + return ""; + } + } + + private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + // TODO + return "--not-yet-supported"; + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java index 73f8d666b..dc1aae0cd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java @@ -22,7 +22,7 @@ public interface Volume { * @param fs * @throws IOException */ - void mount(CryptoFileSystem fs) throws IOException, VolumeException; + void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException; void reveal() throws VolumeException; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java index 2161e388d..118e3a368 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java @@ -11,7 +11,6 @@ import org.cryptomator.frontend.webdav.servlet.WebDavServletController; import javax.inject.Inject; import javax.inject.Provider; - import java.net.InetAddress; import java.net.UnknownHostException; @@ -35,7 +34,7 @@ public class WebDavVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws VolumeException { + public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException { if (server == null) { server = serverProvider.get(); } diff --git a/main/ui/src/main/resources/fxml/unlock.fxml b/main/ui/src/main/resources/fxml/unlock.fxml index 585b60b50..29a9b82e3 100644 --- a/main/ui/src/main/resources/fxml/unlock.fxml +++ b/main/ui/src/main/resources/fxml/unlock.fxml @@ -17,102 +17,81 @@ - + + - + + - + - - - - + + + - - -