From 55d1a8e9352b745a8d01c912bd2037253a8b1a86 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 25 Feb 2022 13:21:45 +0100 Subject: [PATCH] Allow custom mount point for winfsp --- .../mountpoint/CustomMountPointChooser.java | 58 ++++++++++++++----- .../vaultoptions/MountOptionsController.java | 46 +++++---------- .../resources/fxml/vault_options_mount.fxml | 4 +- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index c55ede640..d075e1292 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -4,6 +4,7 @@ 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.MountPointRequirement; import org.cryptomator.common.vaults.Volume; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,9 +13,7 @@ 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; @@ -35,7 +34,6 @@ class CustomMountPointChooser implements MountPointChooser { @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(); } @@ -48,8 +46,16 @@ class CustomMountPointChooser implements MountPointChooser { @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 PARENT_NO_MOUNT_POINT -> { + prepareParentNoMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + return true; + } + case EMPTY_MOUNT_POINT -> { + prepareEmptyMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + return false; + } case NONE -> { //Requirement "NONE" doesn't make any sense here. //No need to prepare/verify a Mountpoint without requiring one... @@ -60,21 +66,26 @@ class CustomMountPointChooser implements MountPointChooser { 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())); + assert SystemUtils.IS_OS_WINDOWS; + + Path hideaway = getHideaway(mountPoint); + if (Files.exists(hideaway)) { + LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount."); + } else if (!Files.isDirectory(mountPoint)) { + throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); //simulate we need a directory + } else { + //TODO: should we require it to be empty? + try { + Files.move(mountPoint, hideaway); + Files.setAttribute(hideaway, "dos:hidden", true); + } catch (IOException e) { + throw new InvalidMountPointException(e); + } } } @@ -92,4 +103,21 @@ class CustomMountPointChooser implements MountPointChooser { } } + @Override + public void cleanup(Volume caller, Path mountPoint) { + if (VolumeImpl.FUSE == caller.getImplementationType() && MountPointRequirement.PARENT_NO_MOUNT_POINT == caller.getMountPointRequirement()) { + Path hideaway = getHideaway(mountPoint); + try { + Files.move(hideaway, mountPoint); + Files.setAttribute(mountPoint, "dos:hidden", false); + } catch (IOException e) { + LOG.error("Unable to clean up mountpoint {} for Winfsp mounting."); + } + } + } + + private Path getHideaway(Path mountPoint) { + return mountPoint.resolveSibling(mountPoint.getFileName().toString() + "_tmp"); + } + } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 8739be791..fc1962b9d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -11,9 +11,6 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; 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; @@ -32,18 +29,16 @@ 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 boolean webDavAndWindows; + private final boolean fuseAndWindows; private final WindowsDriveLetters windowsDriveLetters; private final ResourceBundle resourceBundle; + public CheckBox readOnlyCheckbox; public CheckBox customMountFlagsCheckbox; public TextField mountFlags; @@ -53,20 +48,14 @@ public class MountOptionsController implements FxController { public RadioButton mountPointCustomDir; public ChoiceBox driveLetterSelection; - //FUSE + Windows -> Disable some (experimental) features for the user because they are unstable - //Use argument Dfuse.experimental="true" to override - private final BooleanBinding restrictToStableFuseOnWindows; - @Inject MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) { this.window = window; this.vault = vault; - this.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows); + this.webDavAndWindows = settings.preferredVolumeImpl().get() == VolumeImpl.WEBDAV && SystemUtils.IS_OS_WINDOWS; + this.fuseAndWindows = settings.preferredVolumeImpl().get() == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS; this.windowsDriveLetters = windowsDriveLetters; this.resourceBundle = resourceBundle; - - BooleanBinding isFuseOnWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE).and(osIsWindows); - this.restrictToStableFuseOnWindows = isFuseOnWindows.and(new SimpleBooleanProperty(!environment.useExperimentalFuse())); //Is FUSE on Win and is NOT experimental fuse enabled } @FXML @@ -74,10 +63,11 @@ public class MountOptionsController implements FxController { // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); - if (getRestrictToStableFuseOnWindows()) { + //TODO: support this feature on Windows + if (fuseAndWindows) { readOnlyCheckbox.setSelected(false); // to prevent invalid states + readOnlyCheckbox.setDisable(true); } - readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().or(restrictToStableFuseOnWindows)); // custom mount flags: mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); @@ -95,9 +85,7 @@ public class MountOptionsController implements FxController { driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle)); driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get()); - if (vault.getVaultSettings().useCustomMountPath().get() - && vault.getVaultSettings().getCustomMountPath().isPresent() - && !getRestrictToStableFuseOnWindows() /* to prevent invalid states */) { + if (vault.getVaultSettings().useCustomMountPath().get() && vault.getVaultSettings().getCustomMountPath().isPresent()) { mountPoint.selectToggle(mountPointCustomDir); } else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) { mountPoint.selectToggle(mountPointWinDriveLetter); @@ -188,20 +176,16 @@ public class MountOptionsController implements FxController { // Getter & Setter - public BooleanProperty osIsWindowsProperty() { - return osIsWindows; - } - public boolean getOsIsWindows() { - return osIsWindows.get(); + return SystemUtils.IS_OS_WINDOWS; } - public BooleanBinding webDavAndWindowsProperty() { + public boolean getCustomMountPointSupported() { return webDavAndWindows; } - public boolean isWebDavAndWindows() { - return webDavAndWindows.get(); + public boolean getReadOnlySupported() { + return fuseAndWindows; } public StringProperty customMountPathProperty() { @@ -212,8 +196,4 @@ public class MountOptionsController implements FxController { return vault.getVaultSettings().customMountPath().get(); } - public Boolean getRestrictToStableFuseOnWindows() { - return restrictToStableFuseOnWindows.get(); - } - } diff --git a/src/main/resources/fxml/vault_options_mount.fxml b/src/main/resources/fxml/vault_options_mount.fxml index a96784020..a0d1ce0e1 100644 --- a/src/main/resources/fxml/vault_options_mount.fxml +++ b/src/main/resources/fxml/vault_options_mount.fxml @@ -42,8 +42,8 @@ - - + +