Allow custom mount point for winfsp

This commit is contained in:
Armin Schrenk
2022-02-25 13:21:45 +01:00
parent da2d647361
commit 55d1a8e935
3 changed files with 58 additions and 50 deletions

View File

@@ -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");
}
}

View File

@@ -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();
}
}