diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java new file mode 100644 index 000000000..0d94e8136 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/AvailableDriveLetterChooser.java @@ -0,0 +1,26 @@ +package org.cryptomator.common.mountpoint; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.vaults.WindowsDriveLetters; + +import java.nio.file.Path; +import java.util.Optional; + +public class AvailableDriveLetterChooser implements MountPointChooser { + + private final WindowsDriveLetters windowsDriveLetters; + + public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) { + this.windowsDriveLetters = windowsDriveLetters; + } + + @Override + public boolean isApplicable() { + return SystemUtils.IS_OS_WINDOWS; + } + + @Override + public Optional chooseMountPoint() { + return this.windowsDriveLetters.getAvailableDriveLetterPath(); + } +} diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java new file mode 100644 index 000000000..41bb5b24d --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java @@ -0,0 +1,28 @@ +package org.cryptomator.common.mountpoint; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.Vault; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +public class CustomDriveLetterChooser implements MountPointChooser { + + private final VaultSettings vaultSettings; + + public CustomDriveLetterChooser(Vault vault) { + this.vaultSettings = vault.getVaultSettings(); + } + + @Override + public boolean isApplicable() { + return SystemUtils.IS_OS_WINDOWS; + } + + @Override + public Optional chooseMountPoint() { + return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get); + } +} diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java new file mode 100644 index 000000000..31dcc7aec --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -0,0 +1,69 @@ +package org.cryptomator.common.mountpoint; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.Vault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; +import java.util.Optional; + +public class CustomMountPointChooser implements MountPointChooser { + + private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class); + + private final VaultSettings vaultSettings; + + public CustomMountPointChooser(Vault vault) { + this.vaultSettings = vault.getVaultSettings(); + } + + @Override + public Optional chooseMountPoint() { + //VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used + return this.vaultSettings.getCustomMountPath().map(Paths::get); + } + + @Override + public boolean prepare(Path mountPoint) throws InvalidMountPointException { + //On Windows the target folder MUST NOT exist... + //https://github.com/billziss-gh/winfsp/issues/320 + if (SystemUtils.IS_OS_WINDOWS) { + //We must use #notExists() here because notExists =/= !exists (see docs) + if (Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) { + //File really doesn't exist + return false; + } + //File exists OR can't be determined + throw wrapAsIMPE(new FileAlreadyExistsException(mountPoint.toString())); + } + + //... on Mac and Linux it's the opposite + if (!Files.isDirectory(mountPoint)) { + throw wrapAsIMPE(new NotDirectoryException(mountPoint.toString())); + } + try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) { + if (ds.iterator().hasNext()) { + throw wrapAsIMPE(new DirectoryNotEmptyException(mountPoint.toString())); + } + } catch (IOException exception) { + throw wrapAsIMPE(exception); + } + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + return false; + } + + private InvalidMountPointException wrapAsIMPE(Exception exception) { + return new InvalidMountPointException(exception); + } + +} diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java new file mode 100644 index 000000000..a1dec25ce --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java @@ -0,0 +1,79 @@ +package org.cryptomator.common.mountpoint; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.Environment; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.Vault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +public class TemporaryMountPointChooser implements MountPointChooser { + + private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class); + private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10; + + private final VaultSettings vaultSettings; + private final Environment environment; + + public TemporaryMountPointChooser(Vault vault, Environment environment) { + this.vaultSettings = vault.getVaultSettings(); + this.environment = environment; + } + + @Override + public boolean isApplicable() { + if(this.environment.getMountPointsDir().isEmpty()) { + LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!"); + return false; + } + return true; + } + + @Override + public Optional chooseMountPoint() { + //Shouldn't throw, but let's keep #orElseThrow in case we made a mistake and the check in #isApplicable failed + Path parent = this.environment.getMountPointsDir().orElseThrow(); + String basename = this.vaultSettings.getId(); + for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) { + Path mountPoint = parent.resolve(basename + "_" + i); + if (Files.notExists(mountPoint)) { + return Optional.of(mountPoint); + } + } + return Optional.empty(); + } + + @Override + public boolean prepare(Path mountPoint) throws InvalidMountPointException { + // https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592: + // In order to allow non-admin users to mount FUSE volumes in `/Volumes`, + // starting with version 3.5.0, FUSE will create non-existent mount points automatically. + if (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) { + return false; + } + + try { + //WinFSP needs the parent, but the actual Mountpoint must not exist... + if (SystemUtils.IS_OS_WINDOWS) { + Files.createDirectories(mountPoint.getParent()); + return false; + } else { + //For Linux and Mac the actual Mountpoint must exist + Files.createDirectories(mountPoint); + return true; + } + } catch (IOException exception) { + throw wrapAsIMPE(exception); + } + } + + private InvalidMountPointException wrapAsIMPE(Exception exception) { + return new InvalidMountPointException(exception); + } +}