move mount prepartions to own class

This commit is contained in:
Armin Schrenk
2023-01-13 11:20:23 +01:00
parent 6395f17736
commit a72ea1b9f5
2 changed files with 126 additions and 101 deletions

View File

@@ -0,0 +1,108 @@
package org.cryptomator.common.mount;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountFailedException;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
@Singleton
public class Mounter {
private final Settings settings;
private final Environment env;
private final WindowsDriveLetters driveLetters;
private final ObservableValue<ActualMountService> mountService;
@Inject
public Mounter(Settings settings, Environment env, WindowsDriveLetters driveLetters, ObservableValue<ActualMountService> mountService) {
this.settings = settings;
this.env = env;
this.driveLetters = driveLetters;
this.mountService = mountService;
}
public MountHandle mountAndcreateHandle(VaultSettings vaultSettings, Path cryptoFsRoot) throws IOException, MountFailedException {
var mountService = this.mountService.getValue().service();
var builder = mountService.forFileSystem(cryptoFsRoot);
boolean mountWithinParent = false;
for (var capability : mountService.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
case MOUNT_FLAGS -> builder.setMountFlags(Objects.requireNonNullElse(vaultSettings.mountFlags().getValue(), mountService.getDefaultMountFlags()));
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
}
}
//TODO: refactor logic to own method
var userChosenMountPoint = vaultSettings.getMountPoint();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = mountService.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = mountService.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
var canMountToDir = mountService.hasCapability(MOUNT_TO_EXISTING_DIR);
if (userChosenMountPoint == null) {
if (mountService.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
// no need to set a mount point
} else if (canMountToDriveLetter) {
builder.setMountpoint(driveLetters.getFirstDesiredAvailable().orElseThrow()); //TODO: catch exception
} else if (canMountToParent) {
Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase);
} else if (canMountToDir) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint);
}
} else {
mountWithinParent = canMountToParent && !canMountToDir;
if(mountWithinParent) {
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT
}
try {
builder.setMountpoint(userChosenMountPoint);
} catch (IllegalArgumentException e) {
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
var configNotSupported = (!canMountToDriveLetter && mpIsDriveLetter) || (!canMountToDir && !mpIsDriveLetter) || (!canMountToParent && !mpIsDriveLetter);
if (configNotSupported) {
throw new MountPointNotSupportedException(e.getMessage());
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
//mountpoint must exist
throw new MountPointNotExistsException(e.getMessage());
} else {
throw new IllegalMountPointException(e.getMessage());
}
/*
//TODO:
if (!canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)) {
//parent must exist, mountpoint must not exist
}
*/
}
}
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), mountWithinParent);
}
public record MountHandle(Mount mount, boolean supportsUnmountForced, boolean mountWithinParent) {
}
}

View File

@@ -11,13 +11,8 @@ package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
import org.cryptomator.common.Environment;
import org.cryptomator.common.mount.ActualMountService;
import org.cryptomator.common.mount.IllegalMountPointException;
import org.cryptomator.common.mount.MountPointNotExistsException;
import org.cryptomator.common.mount.MountPointNotSupportedException;
import org.cryptomator.common.mount.Mounter;
import org.cryptomator.common.mount.WindowsDriveLetters;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
@@ -27,11 +22,7 @@ import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.integrations.mount.Mount;
import org.cryptomator.integrations.mount.MountBuilder;
import org.cryptomator.integrations.mount.MountCapability;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
import org.slf4j.Logger;
@@ -48,9 +39,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
@@ -58,11 +47,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_AS_DRIVE_LETTER;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_EXISTING_DIR;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH;
import static org.cryptomator.integrations.mount.MountCapability.MOUNT_WITHIN_EXISTING_PARENT;
@PerVault
public class Vault {
@@ -70,13 +54,10 @@ public class Vault {
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
private final Environment env;
private final Settings settings;
private final VaultSettings vaultSettings;
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
private final VaultState state;
private final ObjectProperty<Exception> lastKnownException;
private final ObservableValue<ActualMountService> mountService;
private final VaultConfigCache configCache;
private final VaultStats stats;
private final StringBinding displayablePath;
@@ -88,20 +69,18 @@ public class Vault {
private final BooleanBinding unknownError;
private final ObjectBinding<Mountpoint> mountPoint;
private final WindowsDriveLetters windowsDriveLetters;
private final Mounter mounter;
private final BooleanProperty showingStats;
private AtomicReference<MountHandle> mountHandle = new AtomicReference<>(null);
private AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
@Inject
Vault(Environment env, Settings settings, VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, ObservableValue<ActualMountService> mountService, VaultStats stats, WindowsDriveLetters windowsDriveLetters) {
this.env = env;
this.settings = settings;
Vault(VaultSettings vaultSettings, VaultConfigCache configCache, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats, WindowsDriveLetters windowsDriveLetters, Mounter mounter) {
this.vaultSettings = vaultSettings;
this.configCache = configCache;
this.cryptoFileSystem = cryptoFileSystem;
this.state = state;
this.lastKnownException = lastKnownException;
this.mountService = mountService;
this.stats = stats;
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
@@ -112,6 +91,7 @@ public class Vault {
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
this.windowsDriveLetters = windowsDriveLetters;
this.mounter = mounter;
this.showingStats = new SimpleBooleanProperty(false);
}
@@ -161,67 +141,6 @@ public class Vault {
}
}
private MountBuilder prepareMount(MountService mountService, Path cryptoRoot) throws IOException {
var builder = mountService.forFileSystem(cryptoRoot);
for (var capability : mountService.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("crypto");
case LOOPBACK_PORT -> builder.setLoopbackPort(settings.port().get()); //TODO: move port from settings to vaultsettings (see https://github.com/cryptomator/cryptomator/tree/feature/mount-setting-per-vault)
case LOOPBACK_HOST_NAME -> env.getLoopbackAlias().ifPresent(builder::setLoopbackHostName);
case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode().get());
case MOUNT_FLAGS -> builder.setMountFlags(Objects.requireNonNullElse(vaultSettings.mountFlags().getValue(), mountService.getDefaultMountFlags()));
case VOLUME_ID -> builder.setVolumeId(vaultSettings.getId());
case VOLUME_NAME -> builder.setVolumeName(vaultSettings.mountName().get());
}
}
var userChosenMountPoint = vaultSettings.getMountPoint();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = mountService.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = mountService.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
var canMountToDir = mountService.hasCapability(MOUNT_TO_EXISTING_DIR);
if (userChosenMountPoint == null) {
if (mountService.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
// no need to set a mount point
} else if (canMountToDriveLetter) {
builder.setMountpoint(windowsDriveLetters.getFirstDesiredAvailable().orElseThrow());
} else if (canMountToParent) {
Files.createDirectories(defaultMountPointBase);
builder.setMountpoint(defaultMountPointBase);
} else if (canMountToDir) {
var mountPoint = defaultMountPointBase.resolve(vaultSettings.mountName().get());
Files.createDirectories(mountPoint);
builder.setMountpoint(mountPoint);
}
} else {
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT?
try {
builder.setMountpoint(userChosenMountPoint);
} catch (IllegalArgumentException e) {
//TODO: move code elsewhere
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
var configNotSupported = (!canMountToDriveLetter && mpIsDriveLetter) || (!canMountToDir && !mpIsDriveLetter) || (!canMountToParent && !mpIsDriveLetter);
if(configNotSupported) {
throw new MountPointNotSupportedException(e.getMessage());
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
//mountpoint must exist
throw new MountPointNotExistsException(e.getMessage());
} else {
throw new IllegalMountPointException(e.getMessage());
}
/*
//TODO:
if (!canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)) {
//parent must exist, mountpoint must not exist
}
*/
}
}
return builder;
}
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, MountFailedException {
if (cryptoFileSystem.get() != null) {
throw new IllegalStateException("Already unlocked.");
@@ -231,9 +150,7 @@ public class Vault {
try {
cryptoFileSystem.set(fs);
var rootPath = fs.getRootDirectories().iterator().next();
var actualMountService = mountService.getValue().service();
var supportsForcedUnmount = actualMountService.hasCapability(MountCapability.UNMOUNT_FORCED);
var mountHandle = new MountHandle(prepareMount(actualMountService, rootPath).mount(), supportsForcedUnmount);
var mountHandle = mounter.mountAndcreateHandle(vaultSettings, rootPath);
success = this.mountHandle.compareAndSet(null, mountHandle);
} finally {
if (!success) {
@@ -242,7 +159,6 @@ public class Vault {
}
}
public synchronized void lock(boolean forced) throws UnmountFailedException, IOException {
var mountHandle = this.mountHandle.get();
if (mountHandle == null) {
@@ -250,14 +166,17 @@ public class Vault {
return;
}
if (forced && mountHandle.supportsUnmountForced) {
mountHandle.mount.unmountForced();
if (forced && mountHandle.supportsUnmountForced()) {
mountHandle.mount().unmountForced();
} else {
mountHandle.mount.unmount();
mountHandle.mount().unmount();
}
try {
mountHandle.mount.close();
mountHandle.mount().close();
if(mountHandle.mountWithinParent()) {
//TODO: cleanup
}
} finally {
destroyCryptoFileSystem();
}
@@ -352,7 +271,7 @@ public class Vault {
public Mountpoint getMountPoint() {
var handle = mountHandle.get();
return handle == null ? null : handle.mount.getMountpoint();
return handle == null ? null : handle.mount().getMountpoint();
}
public StringBinding displayablePathProperty() {
@@ -434,16 +353,14 @@ public class Vault {
}
}
public boolean supportsForcedUnmount() {
var mh = mountHandle.get();
if(mh == null) {
if (mh == null) {
//TODO: or return false?
throw new IllegalStateException("Vault is not mounted");
};
}
;
return mountHandle.get().supportsUnmountForced();
}
private record MountHandle(Mount mount, boolean supportsUnmountForced) {
}
}