Ver Fonte

Merge branch 'feature/3001-more-error-messages' into feature/2856-folder-mounts-win

# Conflicts:
#	src/main/java/org/cryptomator/common/mount/MountWithinParentUtil.java
JaniruTEC há 2 anos atrás
pai
commit
ed6f1ad8d1

+ 17 - 0
src/main/java/org/cryptomator/common/mount/ExistingHideawayException.java

@@ -0,0 +1,17 @@
+package org.cryptomator.common.mount;
+
+import java.nio.file.Path;
+
+public class ExistingHideawayException extends IllegalMountPointException {
+
+	private final Path hideaway;
+
+	public ExistingHideawayException(Path path, Path hideaway, String msg) {
+		super(path, msg);
+		this.hideaway = hideaway;
+	}
+
+	public Path getHideaway() {
+		return hideaway;
+	}
+}

+ 18 - 2
src/main/java/org/cryptomator/common/mount/IllegalMountPointException.java

@@ -1,9 +1,25 @@
 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 IllegalMountPointException(String msg) {
+	private final Path mountpoint;
+
+	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;
+	}
+}

+ 10 - 0
src/main/java/org/cryptomator/common/mount/MountPointCleanupFailedException.java

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

+ 4 - 2
src/main/java/org/cryptomator/common/mount/MountPointInUseException.java

@@ -1,8 +1,10 @@
 package org.cryptomator.common.mount;
 
+import java.nio.file.Path;
+
 public class MountPointInUseException extends IllegalMountPointException {
 
-	public MountPointInUseException(String msg) {
-		super(msg);
+	public MountPointInUseException(Path path) {
+		super(path);
 	}
 }

+ 10 - 0
src/main/java/org/cryptomator/common/mount/MountPointNotEmptyDirectoryException.java

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

+ 14 - 0
src/main/java/org/cryptomator/common/mount/MountPointNotExistingException.java

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

+ 0 - 8
src/main/java/org/cryptomator/common/mount/MountPointNotExistsException.java

@@ -1,8 +0,0 @@
-package org.cryptomator.common.mount;
-
-public class MountPointNotExistsException extends IllegalMountPointException {
-
-	public MountPointNotExistsException(String msg) {
-		super(msg);
-	}
-}

+ 4 - 2
src/main/java/org/cryptomator/common/mount/MountPointNotSupportedException.java

@@ -1,8 +1,10 @@
 package org.cryptomator.common.mount;
 
+import java.nio.file.Path;
+
 public class MountPointNotSupportedException extends IllegalMountPointException {
 
-	public MountPointNotSupportedException(String msg) {
-		super(msg);
+	public MountPointNotSupportedException(Path path, String msg) {
+		super(path, msg);
 	}
 }

+ 0 - 12
src/main/java/org/cryptomator/common/mount/MountPointPreparationException.java

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

+ 14 - 11
src/main/java/org/cryptomator/common/mount/MountWithinParentUtil.java

@@ -5,7 +5,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.nio.file.DirectoryNotEmptyException;
+import java.io.UncheckedIOException;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
@@ -23,14 +23,15 @@ public final class MountWithinParentUtil {
 
 	private MountWithinParentUtil() {}
 
-	static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
+	static void prepareParentNoMountPoint(Path mountPoint) throws IllegalMountPointException {
 		Path hideaway = getHideaway(mountPoint);
 		var mpExists = removeResidualJunction(mountPoint); //Handle junction as not existing
 		var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
 
 		//TODO: possible improvement by just deleting an _empty_ hideaway
+		//TODO: Remove "ExistingHideawayException"
 		if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
-			throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
+			throw new MountPointNotExistingException(mountPoint);
 		} else if (!mpExists) { //only hideaway exists
 			checkIsDirectory(hideaway);
 			LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
@@ -39,7 +40,7 @@ public final class MountWithinParentUtil {
 					Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
 				}
 			} catch (IOException e) {
-				throw new MountPointPreparationException(e);
+				throw new UncheckedIOException(e);
 			}
 		} else { //mountpoint exists...
 			try {
@@ -58,20 +59,21 @@ public final class MountWithinParentUtil {
 				int attempts = 0;
 				while (!Files.notExists(mountPoint)) {
 					if (attempts >= 10) {
-						throw new MountPointPreparationException("Path " + mountPoint + " could not be cleared");
+						throw new MountPointCleanupFailedException(mountPoint);
 					}
 					Thread.sleep(1000);
 					attempts++;
 				}
 			} catch (IOException e) {
-				throw new MountPointPreparationException(e);
+				throw new UncheckedIOException(e);
 			} catch (InterruptedException e) {
 				Thread.currentThread().interrupt();
-				throw new MountPointPreparationException(e);
+				throw new RuntimeException(e);
 			}
 		}
 	}
 
+	//TODO Remove MountPointPreparationException
 	private static boolean removeResidualJunction(Path path) throws MountPointPreparationException {
 		try {
 			if (Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isOther()) {
@@ -87,6 +89,7 @@ public final class MountWithinParentUtil {
 		}
 	}
 
+	//TODO Remove MountPointPreparationException
 	private static void removeResidualHideaway(Path hideaway) throws IOException {
 		if (!Files.isDirectory(hideaway, LinkOption.NOFOLLOW_LINKS)) {
 			throw new MountPointPreparationException(new NotDirectoryException(hideaway.toString()));
@@ -130,16 +133,16 @@ public final class MountWithinParentUtil {
 		}
 	}
 
-	private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
+	private static void checkIsDirectory(Path toCheck) throws IllegalMountPointException {
 		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 checkIsEmpty(Path toCheck) throws IllegalMountPointException, IOException {
 		try (var dirStream = Files.list(toCheck)) {
 			if (dirStream.findFirst().isPresent()) {
-				throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
+				throw new MountPointNotEmptyDirectoryException(toCheck, "Mountpoint directory is not empty: " + toCheck);
 			}
 		}
 	}

+ 4 - 4
src/main/java/org/cryptomator/common/mount/Mounter.java

@@ -99,7 +99,7 @@ public class Mounter {
 				var mpIsDriveLetter = userChosenMountPoint.toString().matches("[A-Z]:\\\\");
 				if (mpIsDriveLetter) {
 					if (driveLetters.getOccupied().contains(userChosenMountPoint)) {
-						throw new MountPointInUseException(userChosenMountPoint.toString());
+						throw new MountPointInUseException(userChosenMountPoint);
 					}
 				} else if (canMountToParent && !canMountToDir) {
 					MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
@@ -115,13 +115,13 @@ public class Mounter {
 							|| (!canMountToParent && !mpIsDriveLetter) //
 							|| (!canMountToDir && !canMountToParent && !canMountToSystem && !canMountToDriveLetter);
 					if (configNotSupported) {
-						throw new MountPointNotSupportedException(e.getMessage());
+						throw new MountPointNotSupportedException(userChosenMountPoint, e.getMessage());
 					} else if (canMountToDir && !canMountToParent && !Files.exists(userChosenMountPoint)) {
 						//mountpoint must exist
-						throw new MountPointNotExistsException(e.getMessage());
+						throw new MountPointNotExistingException(userChosenMountPoint, e.getMessage());
 					} else {
 						//TODO: add specific exception for !canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)
-						throw new IllegalMountPointException(e.getMessage());
+						throw new IllegalMountPointException(userChosenMountPoint, e.getMessage());
 					}
 				}
 			}

+ 16 - 3
src/main/java/org/cryptomator/ui/controls/FormattedLabel.java

@@ -13,18 +13,19 @@ public class FormattedLabel extends Label {
 	private final StringProperty format = new SimpleStringProperty("");
 	private final ObjectProperty<Object> arg1 = new SimpleObjectProperty<>();
 	private final ObjectProperty<Object> arg2 = new SimpleObjectProperty<>();
-	// add arg2, arg3, ... on demand
+	private final ObjectProperty<Object> arg3 = new SimpleObjectProperty<>();
+	// add arg4, arg5, ... on demand
 
 	public FormattedLabel() {
 		textProperty().bind(createStringBinding());
 	}
 
 	protected StringBinding createStringBinding() {
-		return Bindings.createStringBinding(this::updateText, format, arg1, arg2);
+		return Bindings.createStringBinding(this::updateText, format, arg1, arg2, arg3);
 	}
 
 	private String updateText() {
-		return String.format(format.get(), arg1.get(), arg2.get());
+		return String.format(format.get(), arg1.get(), arg2.get(), arg3.get());
 	}
 
 	/* Observables */
@@ -64,4 +65,16 @@ public class FormattedLabel extends Label {
 	public void setArg2(Object arg2) {
 		this.arg2.set(arg2);
 	}
+
+	public ObjectProperty<Object> arg3Property() {
+		return arg3;
+	}
+
+	public Object getArg3() {
+		return arg3.get();
+	}
+
+	public void setArg3(Object arg3) {
+		this.arg3.set(arg3);
+	}
 }

+ 25 - 6
src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java

@@ -1,7 +1,11 @@
 package org.cryptomator.ui.unlock;
 
+import org.cryptomator.common.mount.ExistingHideawayException;
+import org.cryptomator.common.mount.IllegalMountPointException;
+import org.cryptomator.common.mount.MountPointCleanupFailedException;
 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.vaults.Vault;
 import org.cryptomator.ui.common.FxController;
@@ -9,10 +13,12 @@ import org.cryptomator.ui.controls.FormattedLabel;
 import org.cryptomator.ui.fxapp.FxApplicationWindows;
 import org.cryptomator.ui.preferences.SelectedPreferencesTab;
 import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
+import org.jetbrains.annotations.PropertyKey;
 
 import javax.inject.Inject;
 import javafx.fxml.FXML;
 import javafx.stage.Stage;
+import java.nio.file.Path;
 import java.util.ResourceBundle;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -25,26 +31,32 @@ public class UnlockInvalidMountPointController implements FxController {
 	private final FxApplicationWindows appWindows;
 	private final ResourceBundle resourceBundle;
 	private final ExceptionType exceptionType;
+	private final Path exceptionPath;
 	private final String exceptionMessage;
+	private final Path hideawayPath;
 
 	public FormattedLabel dialogDescription;
 
 	@Inject
-	UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<Throwable> unlockException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
+	UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault, @UnlockWindow AtomicReference<IllegalMountPointException> illegalMountPointException, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
 		this.window = window;
 		this.vault = vault;
 		this.appWindows = appWindows;
 		this.resourceBundle = resourceBundle;
 
-		var exc = unlockException.get();
+		var exc = illegalMountPointException.get();
 		this.exceptionType = getExceptionType(exc);
+		this.exceptionPath = exc.getMountpoint();
 		this.exceptionMessage = exc.getMessage();
+		this.hideawayPath = exc instanceof ExistingHideawayException haeExc ? haeExc.getHideaway() : null;
 	}
 
 	@FXML
 	public void initialize() {
 		dialogDescription.setFormat(resourceBundle.getString(exceptionType.translationKey));
-		dialogDescription.setArg1(exceptionMessage);
+		dialogDescription.setArg1(exceptionPath);
+		dialogDescription.setArg2(exceptionMessage);
+		dialogDescription.setArg3(hideawayPath);
 	}
 
 	@FXML
@@ -67,8 +79,11 @@ public class UnlockInvalidMountPointController implements FxController {
 	private ExceptionType getExceptionType(Throwable unlockException) {
 		return switch (unlockException) {
 			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 ExistingHideawayException x -> ExceptionType.HIDEAWAY_EXISTS;
+			case MountPointCleanupFailedException x -> ExceptionType.COULD_NOT_BE_CLEARED;
+			case MountPointNotEmptyDirectoryException x -> ExceptionType.NOT_EMPTY_DIRECTORY;
 			default -> ExceptionType.GENERIC;
 		};
 	}
@@ -78,12 +93,15 @@ public class UnlockInvalidMountPointController implements FxController {
 		NOT_SUPPORTED("unlock.error.customPath.description.notSupported", ButtonAction.SHOW_PREFERENCES),
 		NOT_EXISTING("unlock.error.customPath.description.notExists", ButtonAction.SHOW_VAULT_OPTIONS),
 		IN_USE("unlock.error.customPath.description.inUse", ButtonAction.SHOW_VAULT_OPTIONS),
+		HIDEAWAY_EXISTS("unlock.error.customPath.description.hideawayExists", 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);
 
 		private final String translationKey;
 		private final ButtonAction action;
 
-		ExceptionType(String translationKey, ButtonAction action) {
+		ExceptionType(@PropertyKey(resourceBundle = "i18n.strings") String translationKey, ButtonAction action) {
 			this.translationKey = translationKey;
 			this.action = action;
 		}
@@ -91,6 +109,7 @@ public class UnlockInvalidMountPointController implements FxController {
 
 	private enum ButtonAction {
 
+		//TODO Add option to show filesystem, e.g. for ExceptionType.HIDEAWAY_EXISTS
 		SHOW_PREFERENCES,
 		SHOW_VAULT_OPTIONS;
 

+ 2 - 1
src/main/java/org/cryptomator/ui/unlock/UnlockModule.java

@@ -4,6 +4,7 @@ import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.IntoMap;
+import org.cryptomator.common.mount.IllegalMountPointException;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.ui.common.DefaultSceneFactory;
 import org.cryptomator.ui.common.FxController;
@@ -61,7 +62,7 @@ abstract class UnlockModule {
 	@Provides
 	@UnlockWindow
 	@UnlockScoped
-	static AtomicReference<Throwable> unlockException() {
+	static AtomicReference<IllegalMountPointException> illegalMountPointException() {
 		return new AtomicReference<>();
 	}
 

+ 4 - 4
src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java

@@ -40,10 +40,10 @@ public class UnlockWorkflow extends Task<Boolean> {
 	private final Lazy<Scene> invalidMountPointScene;
 	private final FxApplicationWindows appWindows;
 	private final KeyLoadingStrategy keyLoadingStrategy;
-	private final AtomicReference<Throwable> unlockFailedException;
+	private final AtomicReference<IllegalMountPointException> illegalMountPointException;
 
 	@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 AtomicReference<IllegalMountPointException> illegalMountPointException) {
 		this.window = window;
 		this.vault = vault;
 		this.vaultService = vaultService;
@@ -51,7 +51,7 @@ public class UnlockWorkflow extends Task<Boolean> {
 		this.invalidMountPointScene = invalidMountPointScene;
 		this.appWindows = appWindows;
 		this.keyLoadingStrategy = keyLoadingStrategy;
-		this.unlockFailedException = unlockFailedException;
+		this.illegalMountPointException = illegalMountPointException;
 	}
 
 	@Override
@@ -79,7 +79,7 @@ public class UnlockWorkflow extends Task<Boolean> {
 
 	private void handleIllegalMountPointError(IllegalMountPointException impe) {
 		Platform.runLater(() -> {
-			unlockFailedException.set(impe);
+			illegalMountPointException.set(impe);
 			window.setScene(invalidMountPointScene.get());
 			window.show();
 		});

+ 4 - 1
src/main/resources/i18n/strings.properties

@@ -131,7 +131,10 @@ 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.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.generic=You have selected a custom mount path for this vault, but using it failed with the message: %s
+unlock.error.customPath.description.hideawayExists=The folder "%3$s", which is used to preserve folder properties set by you, has not been automatically cleaned up since your last mount to "%1$s". Please delete it manually and restore any properties to the original mount path if you set any.
+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.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.