mirror of
https://github.com/cryptomator/cryptomator.git
synced 2024-11-23 12:09:45 +00:00
Merge pull request #2996 from cryptomator/feature/2856-folder-mounts-win
Improve handling of folder-mounts on Win
This commit is contained in:
commit
221b4e85bc
@ -0,0 +1,17 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class HideawayNotDirectoryException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
private final Path hideaway;
|
||||||
|
|
||||||
|
public HideawayNotDirectoryException(Path path, Path hideaway) {
|
||||||
|
super(path, "Existing hideaway (" + hideaway.toString() + ") for mountpoint is not a directory: " + path.toString());
|
||||||
|
this.hideaway = hideaway;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getHideaway() {
|
||||||
|
return hideaway;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,25 @@
|
|||||||
package org.cryptomator.common.mount;
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that validation or preparation of a mountpoint failed due to a configuration error or an invalid system state.<br>
|
||||||
|
* Instances of this exception are usually caught and displayed to the user in an appropriate fashion, e.g. by {@link org.cryptomator.ui.unlock.UnlockInvalidMountPointController UnlockInvalidMountPointController.}
|
||||||
|
*/
|
||||||
public class IllegalMountPointException extends IllegalArgumentException {
|
public class IllegalMountPointException extends IllegalArgumentException {
|
||||||
|
|
||||||
public IllegalMountPointException(String msg) {
|
private final Path mountpoint;
|
||||||
super(msg);
|
|
||||||
|
public IllegalMountPointException(Path mountpoint) {
|
||||||
|
this(mountpoint, "The provided mountpoint has a problem: " + mountpoint.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IllegalMountPointException(Path mountpoint, String msg) {
|
||||||
|
super(msg);
|
||||||
|
this.mountpoint = mountpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getMountpoint() {
|
||||||
|
return mountpoint;
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class MountPointCleanupFailedException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
public MountPointCleanupFailedException(Path path) {
|
||||||
|
super(path, "Mountpoint could not be cleared: " + path.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
package org.cryptomator.common.mount;
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class MountPointInUseException extends IllegalMountPointException {
|
public class MountPointInUseException extends IllegalMountPointException {
|
||||||
|
|
||||||
public MountPointInUseException(String msg) {
|
public MountPointInUseException(Path path) {
|
||||||
super(msg);
|
super(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class MountPointNotEmptyDirectoryException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
public MountPointNotEmptyDirectoryException(Path path, String msg) {
|
||||||
|
super(path, msg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class MountPointNotExistingException extends IllegalMountPointException {
|
||||||
|
|
||||||
|
public MountPointNotExistingException(Path path, String msg) {
|
||||||
|
super(path, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MountPointNotExistingException(Path path) {
|
||||||
|
super(path, "Mountpoint does not exist: " + path);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package org.cryptomator.common.mount;
|
|
||||||
|
|
||||||
public class MountPointNotExistsException extends IllegalMountPointException {
|
|
||||||
|
|
||||||
public MountPointNotExistsException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,10 @@
|
|||||||
package org.cryptomator.common.mount;
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class MountPointNotSupportedException extends IllegalMountPointException {
|
public class MountPointNotSupportedException extends IllegalMountPointException {
|
||||||
|
|
||||||
public MountPointNotSupportedException(String msg) {
|
public MountPointNotSupportedException(Path path, String msg) {
|
||||||
super(msg);
|
super(path, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package org.cryptomator.common.mount;
|
|
||||||
|
|
||||||
public class MountPointPreparationException extends RuntimeException {
|
|
||||||
|
|
||||||
public MountPointPreparationException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MountPointPreparationException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,13 +5,11 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
public final class MountWithinParentUtil {
|
public final class MountWithinParentUtil {
|
||||||
|
|
||||||
@ -22,31 +20,33 @@ public final class MountWithinParentUtil {
|
|||||||
|
|
||||||
private MountWithinParentUtil() {}
|
private MountWithinParentUtil() {}
|
||||||
|
|
||||||
static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
|
static void prepareParentNoMountPoint(Path mountPoint) throws IllegalMountPointException, IOException {
|
||||||
Path hideaway = getHideaway(mountPoint);
|
Path hideaway = getHideaway(mountPoint);
|
||||||
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
|
var mpState = getMountPointState(mountPoint);
|
||||||
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
|
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
|
||||||
//TODO: possible improvement by just deleting an _empty_ hideaway
|
if (mpState == MountPointState.BROKEN_JUNCTION) {
|
||||||
if (mpExists && hideExists) { //both resources exist (whatever type)
|
LOG.info("Mountpoint \"{}\" is still a junction. Deleting it.", mountPoint);
|
||||||
throw new MountPointPreparationException(new FileAlreadyExistsException(hideaway.toString()));
|
Files.delete(mountPoint); //Throws if mountPoint is also a non-empty folder
|
||||||
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
|
mpState = MountPointState.NOT_EXISTING;
|
||||||
throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
|
}
|
||||||
} else if (!mpExists) { //only hideaway exists
|
|
||||||
checkIsDirectory(hideaway);
|
if (mpState == MountPointState.NOT_EXISTING && !hideExists) { //neither mountpoint nor hideaway exist
|
||||||
|
throw new MountPointNotExistingException(mountPoint);
|
||||||
|
} else if (mpState == MountPointState.NOT_EXISTING) { //only hideaway exists
|
||||||
|
checkIsHideawayDirectory(mountPoint, hideaway);
|
||||||
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
|
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
|
||||||
try {
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} else {
|
||||||
throw new MountPointPreparationException(e);
|
assert mpState == MountPointState.EMPTY_DIR;
|
||||||
}
|
|
||||||
} else { //only mountpoint exists
|
|
||||||
try {
|
try {
|
||||||
checkIsDirectory(mountPoint);
|
if (hideExists) { //... with hideaway
|
||||||
checkIsEmpty(mountPoint);
|
removeResidualHideaway(mountPoint, hideaway);
|
||||||
|
}
|
||||||
|
|
||||||
|
//... (now) without hideaway
|
||||||
Files.move(mountPoint, hideaway);
|
Files.move(mountPoint, hideaway);
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
|
||||||
@ -54,30 +54,66 @@ public final class MountWithinParentUtil {
|
|||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (!Files.notExists(mountPoint)) {
|
while (!Files.notExists(mountPoint)) {
|
||||||
if (attempts >= 10) {
|
if (attempts >= 10) {
|
||||||
throw new MountPointPreparationException("Path " + mountPoint + " could not be cleared");
|
throw new MountPointCleanupFailedException(mountPoint);
|
||||||
}
|
}
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
throw new MountPointPreparationException(e);
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
throw new MountPointPreparationException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//visible for testing
|
||||||
|
static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException {
|
||||||
|
if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
return MountPointState.NOT_EXISTING;
|
||||||
|
}
|
||||||
|
if (!Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isOther()) {
|
||||||
|
checkIsMountPointDirectory(path);
|
||||||
|
checkIsMountPointEmpty(path);
|
||||||
|
return MountPointState.EMPTY_DIR;
|
||||||
|
}
|
||||||
|
if (Files.exists(path /* FOLLOW_LINKS */)) { //Both junction and target exist
|
||||||
|
throw new MountPointInUseException(path);
|
||||||
|
}
|
||||||
|
return MountPointState.BROKEN_JUNCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
//visible for testing
|
||||||
|
enum MountPointState {
|
||||||
|
|
||||||
|
NOT_EXISTING,
|
||||||
|
|
||||||
|
EMPTY_DIR,
|
||||||
|
|
||||||
|
BROKEN_JUNCTION;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//visible for testing
|
||||||
|
static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException {
|
||||||
|
checkIsHideawayDirectory(mountPoint, hideaway);
|
||||||
|
Files.delete(hideaway); //Fails if not empty
|
||||||
|
}
|
||||||
|
|
||||||
static void cleanup(Path mountPoint) {
|
static void cleanup(Path mountPoint) {
|
||||||
Path hideaway = getHideaway(mountPoint);
|
Path hideaway = getHideaway(mountPoint);
|
||||||
try {
|
try {
|
||||||
waitForMountpointRestoration(mountPoint);
|
waitForMountpointRestoration(mountPoint);
|
||||||
|
if (Files.notExists(hideaway, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
LOG.error("Unable to restore hidden directory to mountpoint \"{}\": Directory does not exist.", mountPoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Files.move(hideaway, mountPoint);
|
Files.move(hideaway, mountPoint);
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
|
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Unable to restore hidden directory to mountpoint {}.", mountPoint, e);
|
LOG.error("Unable to restore hidden directory to mountpoint \"{}\".", mountPoint, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,16 +135,22 @@ public final class MountWithinParentUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
|
private static void checkIsMountPointDirectory(Path toCheck) throws IllegalMountPointException {
|
||||||
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
|
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw new MountPointPreparationException(new NotDirectoryException(toCheck.toString()));
|
throw new MountPointNotEmptyDirectoryException(toCheck, "Mountpoint is not a directory: " + toCheck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkIsEmpty(Path toCheck) throws MountPointPreparationException, IOException {
|
private static void checkIsHideawayDirectory(Path mountPoint, Path hideawayToCheck) {
|
||||||
|
if (!Files.isDirectory(hideawayToCheck, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
throw new HideawayNotDirectoryException(mountPoint, hideawayToCheck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkIsMountPointEmpty(Path toCheck) throws IllegalMountPointException, IOException {
|
||||||
try (var dirStream = Files.list(toCheck)) {
|
try (var dirStream = Files.list(toCheck)) {
|
||||||
if (dirStream.findFirst().isPresent()) {
|
if (dirStream.findFirst().isPresent()) {
|
||||||
throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
|
throw new MountPointNotEmptyDirectoryException(toCheck, "Mountpoint directory is not empty: " + toCheck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ public class Mounter {
|
|||||||
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
|
var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
|
||||||
if (mpIsDriveLetter) {
|
if (mpIsDriveLetter) {
|
||||||
if (driveLetters.getOccupied().contains(userChosenMountPoint)) {
|
if (driveLetters.getOccupied().contains(userChosenMountPoint)) {
|
||||||
throw new MountPointInUseException(userChosenMountPoint.toString());
|
throw new MountPointInUseException(userChosenMountPoint);
|
||||||
}
|
}
|
||||||
} else if (canMountToParent && !canMountToDir) {
|
} else if (canMountToParent && !canMountToDir) {
|
||||||
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
||||||
@ -115,13 +115,13 @@ public class Mounter {
|
|||||||
|| (!canMountToParent && !mpIsDriveLetter) //
|
|| (!canMountToParent && !mpIsDriveLetter) //
|
||||||
|| (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter);
|
|| (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter);
|
||||||
if (configNotSupported) {
|
if (configNotSupported) {
|
||||||
throw new MountPointNotSupportedException(e.getMessage());
|
throw new MountPointNotSupportedException(userChosenMountPoint, e.getMessage());
|
||||||
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
|
} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
|
||||||
//mountpoint must exist
|
//mountpoint must exist
|
||||||
throw new MountPointNotExistsException(e.getMessage());
|
throw new MountPointNotExistingException(userChosenMountPoint, e.getMessage());
|
||||||
} else {
|
} else {
|
||||||
//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
|
//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
|
||||||
throw new IllegalMountPointException(e.getMessage());
|
throw new IllegalMountPointException(userChosenMountPoint, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import org.cryptomator.common.ObservableUtil;
|
||||||
|
import org.cryptomator.common.mount.HideawayNotDirectoryException;
|
||||||
|
import org.cryptomator.common.mount.IllegalMountPointException;
|
||||||
|
import org.cryptomator.common.mount.MountPointCleanupFailedException;
|
||||||
import org.cryptomator.common.mount.MountPointInUseException;
|
import org.cryptomator.common.mount.MountPointInUseException;
|
||||||
import org.cryptomator.common.mount.MountPointNotExistsException;
|
import org.cryptomator.common.mount.MountPointNotEmptyDirectoryException;
|
||||||
|
import org.cryptomator.common.mount.MountPointNotExistingException;
|
||||||
import org.cryptomator.common.mount.MountPointNotSupportedException;
|
import org.cryptomator.common.mount.MountPointNotSupportedException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@ -9,12 +14,15 @@ import org.cryptomator.ui.controls.FormattedLabel;
|
|||||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||||
|
import org.jetbrains.annotations.PropertyKey;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
@ -23,28 +31,31 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final ResourceBundle resourceBundle;
|
|
||||||
private final ExceptionType exceptionType;
|
private final ObservableValue<ExceptionType> exceptionType;
|
||||||
private final String exceptionMessage;
|
private final ObservableValue<Path> exceptionPath;
|
||||||
|
private final ObservableValue<String> exceptionMessage;
|
||||||
|
private final ObservableValue<Path> hideawayPath;
|
||||||
|
private final ObservableValue<String> format;
|
||||||
|
private final ObservableValue<Boolean> showPreferences;
|
||||||
|
private final ObservableValue<Boolean> showVaultOptions;
|
||||||
|
|
||||||
public FormattedLabel dialogDescription;
|
public FormattedLabel dialogDescription;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
|
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.resourceBundle = resourceBundle;
|
|
||||||
|
|
||||||
var exc = unlockException.get();
|
this.exceptionType = illegalMountPointException.map(this::getExceptionType);
|
||||||
this.exceptionType = getExceptionType(exc);
|
this.exceptionPath = illegalMountPointException.map(IllegalMountPointException::getMountpoint);
|
||||||
this.exceptionMessage = exc.getMessage();
|
this.exceptionMessage = illegalMountPointException.map(IllegalMountPointException::getMessage);
|
||||||
}
|
this.hideawayPath = illegalMountPointException.map(e -> e instanceof HideawayNotDirectoryException haeExc ? haeExc.getHideaway() : null);
|
||||||
|
|
||||||
@FXML
|
this.format = ObservableUtil.mapWithDefault(exceptionType, type -> resourceBundle.getString(type.translationKey), "");
|
||||||
public void initialize() {
|
this.showPreferences = ObservableUtil.mapWithDefault(exceptionType, type -> type.action == ButtonAction.SHOW_PREFERENCES, false);
|
||||||
dialogDescription.setFormat(resourceBundle.getString(exceptionType.translationKey));
|
this.showVaultOptions = ObservableUtil.mapWithDefault(exceptionType, type -> type.action == ButtonAction.SHOW_VAULT_OPTIONS, false);
|
||||||
dialogDescription.setArg1(exceptionMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@ -67,8 +78,11 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
private ExceptionType getExceptionType(Throwable unlockException) {
|
private ExceptionType getExceptionType(Throwable unlockException) {
|
||||||
return switch (unlockException) {
|
return switch (unlockException) {
|
||||||
case MountPointNotSupportedException x -> ExceptionType.NOT_SUPPORTED;
|
case MountPointNotSupportedException x -> ExceptionType.NOT_SUPPORTED;
|
||||||
case MountPointNotExistsException x -> ExceptionType.NOT_EXISTING;
|
case MountPointNotExistingException x -> ExceptionType.NOT_EXISTING;
|
||||||
case MountPointInUseException x -> ExceptionType.IN_USE;
|
case MountPointInUseException x -> ExceptionType.IN_USE;
|
||||||
|
case HideawayNotDirectoryException x -> ExceptionType.HIDEAWAY_NOT_DIR;
|
||||||
|
case MountPointCleanupFailedException x -> ExceptionType.COULD_NOT_BE_CLEARED;
|
||||||
|
case MountPointNotEmptyDirectoryException x -> ExceptionType.NOT_EMPTY_DIRECTORY;
|
||||||
default -> ExceptionType.GENERIC;
|
default -> ExceptionType.GENERIC;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -78,12 +92,15 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES),
|
NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES),
|
||||||
NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS),
|
NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS),
|
||||||
IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS),
|
IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS),
|
||||||
|
HIDEAWAY_NOT_DIR("unlock.error.customPath.description.hideawayNotDir", ButtonAction.SHOW_VAULT_OPTIONS),
|
||||||
|
COULD_NOT_BE_CLEARED("unlock.error.customPath.description.couldNotBeCleaned", ButtonAction.SHOW_VAULT_OPTIONS),
|
||||||
|
NOT_EMPTY_DIRECTORY("unlock.error.customPath.description.notEmptyDir", ButtonAction.SHOW_VAULT_OPTIONS),
|
||||||
GENERIC("unlock.error.customPath.description.generic", ButtonAction.SHOW_PREFERENCES);
|
GENERIC("unlock.error.customPath.description.generic", ButtonAction.SHOW_PREFERENCES);
|
||||||
|
|
||||||
private final String translationKey;
|
private final String translationKey;
|
||||||
private final ButtonAction action;
|
private final ButtonAction action;
|
||||||
|
|
||||||
ExceptionType(String translationKey, ButtonAction action) {
|
ExceptionType(@PropertyKey(resourceBundle = "i18n.strings") String translationKey, ButtonAction action) {
|
||||||
this.translationKey = translationKey;
|
this.translationKey = translationKey;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
@ -91,6 +108,7 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
|
|
||||||
private enum ButtonAction {
|
private enum ButtonAction {
|
||||||
|
|
||||||
|
//TODO Add option to show filesystem, e.g. for ExceptionType.HIDEAWAY_EXISTS
|
||||||
SHOW_PREFERENCES,
|
SHOW_PREFERENCES,
|
||||||
SHOW_VAULT_OPTIONS;
|
SHOW_VAULT_OPTIONS;
|
||||||
|
|
||||||
@ -98,11 +116,51 @@ public class UnlockInvalidMountPointController implements FxController {
|
|||||||
|
|
||||||
/* Getter */
|
/* Getter */
|
||||||
|
|
||||||
public boolean isShowPreferences() {
|
public Path getExceptionPath() {
|
||||||
return exceptionType.action == ButtonAction.SHOW_PREFERENCES;
|
return exceptionPath.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowVaultOptions() {
|
public ObservableValue<Path> exceptionPathProperty() {
|
||||||
return exceptionType.action == ButtonAction.SHOW_VAULT_OPTIONS;
|
return exceptionPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFormat() {
|
||||||
|
return format.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<String> formatProperty() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExceptionMessage() {
|
||||||
|
return exceptionMessage.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<String> exceptionMessageProperty() {
|
||||||
|
return exceptionMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getHideawayPath() {
|
||||||
|
return hideawayPath.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Path> hideawayPathProperty() {
|
||||||
|
return hideawayPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getShowPreferences() {
|
||||||
|
return showPreferences.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> showPreferencesProperty() {
|
||||||
|
return showPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getShowVaultOptions() {
|
||||||
|
return showVaultOptions.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> showVaultOptionsProperty() {
|
||||||
|
return showVaultOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
|
import org.cryptomator.common.mount.IllegalMountPointException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@ -18,12 +19,13 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
@Module(subcomponents = {KeyLoadingComponent.class})
|
@Module(subcomponents = {KeyLoadingComponent.class})
|
||||||
abstract class UnlockModule {
|
abstract class UnlockModule {
|
||||||
@ -61,8 +63,8 @@ abstract class UnlockModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@UnlockWindow
|
@UnlockWindow
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
static AtomicReference<Throwable> unlockException() {
|
static ObjectProperty<IllegalMountPointException> illegalMountPointException() {
|
||||||
return new AtomicReference<>();
|
return new SimpleObjectProperty<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -17,11 +17,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multi-step task that consists of background activities as well as user interaction.
|
* A multi-step task that consists of background activities as well as user interaction.
|
||||||
@ -40,10 +40,10 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
private final Lazy<Scene> invalidMountPointScene;
|
private final Lazy<Scene> invalidMountPointScene;
|
||||||
private final FxApplicationWindows appWindows;
|
private final FxApplicationWindows appWindows;
|
||||||
private final KeyLoadingStrategy keyLoadingStrategy;
|
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||||
private final AtomicReference<Throwable> unlockFailedException;
|
private final ObjectProperty<IllegalMountPointException> illegalMountPointException;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow AtomicReference<Throwable> unlockFailedException) {
|
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, FxApplicationWindows appWindows, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, @UnlockWindow ObjectProperty<IllegalMountPointException> illegalMountPointException) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
@ -51,7 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
this.invalidMountPointScene = invalidMountPointScene;
|
this.invalidMountPointScene = invalidMountPointScene;
|
||||||
this.appWindows = appWindows;
|
this.appWindows = appWindows;
|
||||||
this.keyLoadingStrategy = keyLoadingStrategy;
|
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||||
this.unlockFailedException = unlockFailedException;
|
this.illegalMountPointException = illegalMountPointException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -79,7 +79,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
|
|
||||||
private void handleIllegalMountPointError(IllegalMountPointException impe) {
|
private void handleIllegalMountPointError(IllegalMountPointException impe) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
unlockFailedException.set(impe);
|
illegalMountPointException.set(impe);
|
||||||
window.setScene(invalidMountPointScene.get());
|
window.setScene(invalidMountPointScene.get());
|
||||||
window.show();
|
window.show();
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<FormattedLabel fx:id="dialogDescription" wrapText="true" textAlignment="LEFT"/>
|
<FormattedLabel fx:id="dialogDescription" wrapText="true" textAlignment="LEFT" format="${controller.format}" arg1="${controller.exceptionPath}" arg2="${controller.exceptionMessage}" arg3="${controller.hideawayPath}"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||||
|
@ -136,8 +136,11 @@ unlock.success.revealBtn=Reveal Drive
|
|||||||
unlock.error.customPath.message=Unable to mount vault to custom path
|
unlock.error.customPath.message=Unable to mount vault to custom path
|
||||||
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
|
unlock.error.customPath.description.notSupported=If you wish to keep using the custom path, please go to the preferences and select a volume type that supports it. Otherwise, go to the vault options and choose a supported mount point.
|
||||||
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
|
unlock.error.customPath.description.notExists=The custom mount path does not exist. Either create it in your local filesystem or change it in the vault options.
|
||||||
unlock.error.customPath.description.inUse=Drive letter "%s" is already in use.
|
unlock.error.customPath.description.inUse=The drive letter or custom mount path "%s" is already in use.
|
||||||
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s
|
unlock.error.customPath.description.hideawayNotDir=The temporary, hidden file "%3$s" used for unlock could not be removed. Please check the file and then delete it manually.
|
||||||
|
unlock.error.customPath.description.couldNotBeCleaned=Your vault could not be mounted to the path "%s". Please try again or choose a different path.
|
||||||
|
unlock.error.customPath.description.notEmptyDir=The custom mount path "%s" is not an empty folder. Please choose an empty folder and try again.
|
||||||
|
unlock.error.customPath.description.generic=You have selected a custom mount path for this vault, but using it failed with the message: %2$s
|
||||||
## Hub
|
## Hub
|
||||||
hub.noKeychain.message=Unable to access device key
|
hub.noKeychain.message=Unable to access device key
|
||||||
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
|
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
|
||||||
|
@ -0,0 +1,241 @@
|
|||||||
|
package org.cryptomator.common.mount;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.OS;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.MountPointState.EMPTY_DIR;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.MountPointState.NOT_EXISTING;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.cleanup;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.getHideaway;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.getMountPointState;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.prepareParentNoMountPoint;
|
||||||
|
import static org.cryptomator.common.mount.MountWithinParentUtil.removeResidualHideaway;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
|
|
||||||
|
class MountWithinParentUtilTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareNeitherExist(@TempDir Path parentDir) {
|
||||||
|
assertThrows(MountPointNotExistingException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(parentDir.resolve("mount"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareOnlyHideawayFileExists(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createFile(hideaway);
|
||||||
|
|
||||||
|
assertThrows(HideawayNotDirectoryException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(mount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareOnlyHideawayDirExists(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createDirectory(hideaway);
|
||||||
|
assertFalse(isHidden(hideaway));
|
||||||
|
|
||||||
|
prepareParentNoMountPoint(mount);
|
||||||
|
|
||||||
|
assumeTrue(SystemUtils.IS_OS_WINDOWS);
|
||||||
|
assertTrue(isHidden(hideaway));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareBothExistHideawayFile(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createFile(hideaway);
|
||||||
|
Files.createDirectory(mount);
|
||||||
|
|
||||||
|
assertThrows(HideawayNotDirectoryException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(mount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareBothExistHideawayNotEmpty(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createDirectory(hideaway);
|
||||||
|
Files.createFile(hideaway.resolve("dummy"));
|
||||||
|
Files.createDirectory(mount);
|
||||||
|
|
||||||
|
assertThrows(DirectoryNotEmptyException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(mount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareBothExistMountNotDir(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createFile(hideaway);
|
||||||
|
Files.createFile(mount);
|
||||||
|
|
||||||
|
assertThrows(MountPointNotEmptyDirectoryException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(mount); //Must not throw something related to hideaway
|
||||||
|
});
|
||||||
|
assertTrue(Files.exists(hideaway, NOFOLLOW_LINKS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareBothExistMountNotEmpty(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createFile(hideaway);
|
||||||
|
Files.createDirectory(mount);
|
||||||
|
Files.createFile(mount.resolve("dummy"));
|
||||||
|
|
||||||
|
assertThrows(MountPointNotEmptyDirectoryException.class, () -> {
|
||||||
|
prepareParentNoMountPoint(mount); //Must not throw something related to hideaway
|
||||||
|
});
|
||||||
|
assertTrue(Files.exists(hideaway, NOFOLLOW_LINKS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPrepareBothExist(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createDirectory(hideaway);
|
||||||
|
Files.createDirectory(mount);
|
||||||
|
|
||||||
|
prepareParentNoMountPoint(mount);
|
||||||
|
assertTrue(Files.notExists(mount, NOFOLLOW_LINKS));
|
||||||
|
|
||||||
|
assumeTrue(SystemUtils.IS_OS_WINDOWS);
|
||||||
|
assertTrue(isHidden(hideaway));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHandleMountPointFolderDoesNotExist(@TempDir Path parentDir) throws IOException {
|
||||||
|
assertSame(getMountPointState(parentDir.resolve("notExisting")), NOT_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHandleMountPointFolderIsFile(@TempDir Path parentDir) throws IOException {
|
||||||
|
var regularFile = parentDir.resolve("regularFile");
|
||||||
|
Files.createFile(regularFile);
|
||||||
|
|
||||||
|
assertThrows(MountPointNotEmptyDirectoryException.class, () -> {
|
||||||
|
getMountPointState(regularFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHandleMountPointFolderIsNotEmpty(@TempDir Path parentDir) throws IOException {
|
||||||
|
var regularFolder = parentDir.resolve("regularFolder");
|
||||||
|
var dummyFile = regularFolder.resolve("dummy");
|
||||||
|
Files.createDirectory(regularFolder);
|
||||||
|
Files.createFile(dummyFile);
|
||||||
|
|
||||||
|
assertThrows(MountPointNotEmptyDirectoryException.class, () -> {
|
||||||
|
getMountPointState(regularFolder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHandleMountPointFolder(@TempDir Path parentDir) throws IOException {
|
||||||
|
//Sadly can't easily create files with "Other" attribute
|
||||||
|
var regularFolder = parentDir.resolve("regularFolder");
|
||||||
|
Files.createDirectory(regularFolder);
|
||||||
|
|
||||||
|
assertSame(getMountPointState(regularFolder), EMPTY_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveResidualHideawayFile(@TempDir Path parentDir) throws IOException {
|
||||||
|
var hideaway = parentDir.resolve("hideaway");
|
||||||
|
Files.createFile(hideaway);
|
||||||
|
|
||||||
|
assertThrows(HideawayNotDirectoryException.class, () -> removeResidualHideaway(parentDir.resolve("mount"), hideaway));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveResidualHideawayNotEmpty(@TempDir Path parentDir) throws IOException {
|
||||||
|
var hideaway = parentDir.resolve("hideaway");
|
||||||
|
Files.createDirectory(hideaway);
|
||||||
|
Files.createFile(hideaway.resolve("dummy"));
|
||||||
|
|
||||||
|
assertThrows(DirectoryNotEmptyException.class, () -> removeResidualHideaway(parentDir.resolve("mount"), hideaway));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCleanupNoHideaway(@TempDir Path parentDir) {
|
||||||
|
assertDoesNotThrow(() -> cleanup(parentDir.resolve("mount")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCleanup(@TempDir Path parentDir) throws IOException {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
Files.createDirectory(hideaway);
|
||||||
|
|
||||||
|
cleanup(mount);
|
||||||
|
assertTrue(Files.exists(mount, NOFOLLOW_LINKS));
|
||||||
|
assertTrue(Files.notExists(hideaway, NOFOLLOW_LINKS));
|
||||||
|
assertFalse(isHidden(mount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
void testGetHideawayRootDirWin() {
|
||||||
|
var mount = Path.of("C:\\mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
|
||||||
|
assertEquals(mount.getParent(), hideaway.getParent());
|
||||||
|
assertEquals(mount.getParent().resolve(".~$mount.tmp"), hideaway);
|
||||||
|
assertEquals(mount.getParent().toAbsolutePath() + ".~$mount.tmp", hideaway.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnOs(OS.WINDOWS)
|
||||||
|
void testGetHideawayRootDirUnix() {
|
||||||
|
var mount = Path.of("/mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
|
||||||
|
assertEquals(mount.getParent(), hideaway.getParent());
|
||||||
|
assertEquals(mount.getParent().resolve(".~$mount.tmp"), hideaway);
|
||||||
|
assertEquals(mount.getParent().toAbsolutePath() + ".~$mount.tmp", hideaway.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHideaway(@TempDir Path parentDir) {
|
||||||
|
var mount = parentDir.resolve("mount");
|
||||||
|
var hideaway = getHideaway(mount);
|
||||||
|
|
||||||
|
assertEquals(mount.getParent(), hideaway.getParent());
|
||||||
|
assertEquals(mount.getParent().resolve(".~$mount.tmp"), hideaway);
|
||||||
|
assertEquals(mount.getParent().toAbsolutePath() + File.separator + ".~$mount.tmp", hideaway.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHidden(Path path) throws IOException {
|
||||||
|
try {
|
||||||
|
return (boolean) Objects.requireNonNullElse(Files.getAttribute(path, "dos:hidden", NOFOLLOW_LINKS), false);
|
||||||
|
} catch (UnsupportedOperationException | IllegalMountPointException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user