Browse Source

Allow custom mount point for winfsp

Armin Schrenk 3 năm trước cách đây
mục cha
commit
55d1a8e935

+ 43 - 15
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");
+	}
+
 }

+ 13 - 33
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<String> 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();
-	}
-
 }

+ 2 - 2
src/main/resources/fxml/vault_options_mount.fxml

@@ -42,8 +42,8 @@
 			<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointWinDriveLetter" text="%vaultOptions.mount.mountPoint.driveLetter"/>
 			<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointWinDriveLetter.selected}"/>
 		</HBox>
-		<HBox spacing="6" alignment="CENTER_LEFT" visible="${!controller.webDavAndWindows}" managed="${!controller.webDavAndWindows}">
-			<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom" disable="${controller.restrictToStableFuseOnWindows}"/>
+		<HBox spacing="6" alignment="CENTER_LEFT" visible="${!controller.customMountPointSupported}" managed="${!controller.customMountPointSupported}">
+			<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom" />
 			<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointCustomDir.selected}">
 				<graphic>
 					<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>