diff --git a/src/main/java/org/cryptomator/common/vaults/NotAVaultDirectoryException.java b/src/main/java/org/cryptomator/common/vaults/NotAVaultDirectoryException.java new file mode 100644 index 000000000..a460621d9 --- /dev/null +++ b/src/main/java/org/cryptomator/common/vaults/NotAVaultDirectoryException.java @@ -0,0 +1,32 @@ +package org.cryptomator.common.vaults; + +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; + +public class NotAVaultDirectoryException extends NoSuchFileException { + + public enum Reason { + MISSING_DATA_DIR, + DATA_NOT_A_DIRECTORY, + MISSING_VAULT_CONFIG, + VAULT_CONFIG_ACCESS_DENIED, + UNSUPPORTED_STRUCTURE + } + + private final transient Path path; + private final Reason reason; + + public NotAVaultDirectoryException(Path path, Reason reason) { + super(path.toString(), null, "Not a vault directory: " + reason); + this.path = path; + this.reason = reason; + } + + public Path path() { + return path; + } + + public Reason notAVaultReason() { + return reason; + } +} diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 8cbafcd37..6fb5dbf7b 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -25,11 +25,9 @@ import javax.inject.Singleton; import javafx.collections.ObservableList; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.ResourceBundle; @@ -85,40 +83,28 @@ public class VaultListManager { }); } - static void assertIsVaultDirectory(Path pathToVault) throws IOException { + public static void assertIsVaultDirectory(Path pathToVault) throws IOException { if (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) { - throw new NoSuchFileException(pathToVault.toString(), null, "Not a vault directory: " + determineNotVaultDirectoryReason(pathToVault)); - } - } + Path dataDir = pathToVault.resolve(DATA_DIR_NAME); + if (!Files.isDirectory(dataDir)) { + if (Files.exists(dataDir)) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.DATA_NOT_A_DIRECTORY); + } else { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_DATA_DIR); + } + } - private static String determineNotVaultDirectoryReason(Path pathToVault) { - Path dataDir = pathToVault.resolve(DATA_DIR_NAME); - if (!Files.isDirectory(dataDir)) { - return describeNotDirectory(dataDir); - } + Path vaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME); + if (!Files.isReadable(vaultConfig)) { + if (Files.exists(vaultConfig)) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.VAULT_CONFIG_ACCESS_DENIED); + } else { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG); + } + } - Path vaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME); - if (!Files.isReadable(vaultConfig)) { - Path masterkey = pathToVault.resolve(MASTERKEY_FILENAME); - return describeNotReadable(vaultConfig) + "; " + describeNotReadable(masterkey) + " for legacy vault detection"; - } - - return "directory structure is unsupported"; - } - - private static String describeNotDirectory(Path path) { - if (Files.exists(path)) { - return path.getFileName() + " is not a directory"; - } else { - return path.getFileName() + " directory is missing"; - } - } - - private static String describeNotReadable(Path path) { - if (Files.exists(path)) { - return path.getFileName() + " is not readable"; - } else { - return path.getFileName() + " is missing"; + //if vault is legacy _and_ not readable, just say unsupported + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.UNSUPPORTED_STRUCTURE); } } @@ -189,7 +175,7 @@ public class VaultListManager { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME); } - case VAULT_CONFIG_MISSING -> { + case VAULT_CONFIG_MISSING -> { //Nothing to do here, since there is no config to read } case MISSING, ALL_MISSING, ERROR, PROCESSING -> { diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 7862c3b20..8399d6c61 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -2,12 +2,14 @@ package org.cryptomator.ui.addvaultwizard; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.vaults.NotAVaultDirectoryException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.integrations.uiappearance.Theme; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationStyle; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; @@ -41,6 +43,7 @@ public class ChooseExistingVaultController implements FxController { private final ObjectProperty vault; private final VaultListManager vaultListManager; private final ResourceBundle resourceBundle; + private final Dialogs dialogs; private final ObservableValue screenshot; @Inject @@ -51,6 +54,7 @@ public class ChooseExistingVaultController implements FxController { @AddVaultWizardWindow ObjectProperty vault, // VaultListManager vaultListManager, // ResourceBundle resourceBundle, // + Dialogs dialogs, // FxApplicationStyle applicationStyle) { this.window = window; this.successScene = successScene; @@ -59,6 +63,7 @@ public class ChooseExistingVaultController implements FxController { this.vault = vault; this.vaultListManager = vaultListManager; this.resourceBundle = resourceBundle; + this.dialogs = dialogs; this.screenshot = applicationStyle.appliedAppThemeProperty().map(this::selectScreenshot); } @@ -87,6 +92,9 @@ public class ChooseExistingVaultController implements FxController { Vault newVault = vaultListManager.add(vaultPath.get()); vault.set(newVault); window.setScene(successScene.get()); + } catch (NotAVaultDirectoryException e) { + LOG.warn("Selected folder is not a vault directory: {}", e.getMessage()); + dialogs.prepareNotAVaultDirectoryDialog(window, e.notAVaultReason()).build().showAndWait(); } catch (IOException e) { LOG.error("Failed to open existing vault.", e); appWindows.showErrorWindow(e, window, window.getScene()); diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java index 452acf02a..b614bed49 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.dialogs; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.NotAVaultDirectoryException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.StageFactory; @@ -139,6 +140,24 @@ public class Dialogs { .setCancelAction(Stage::close); } + public SimpleDialog.Builder prepareNotAVaultDirectoryDialog(Stage window, NotAVaultDirectoryException.Reason reason) { + String descriptionKey = switch (reason) { + case MISSING_DATA_DIR -> "addvaultwizard.existing.notAVault.description.missingDataDir"; + case DATA_NOT_A_DIRECTORY -> "addvaultwizard.existing.notAVault.description.dataNotADirectory"; + case MISSING_VAULT_CONFIG -> "addvaultwizard.existing.notAVault.description.missingVaultConfig"; + case VAULT_CONFIG_ACCESS_DENIED -> "addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied"; + case UNSUPPORTED_STRUCTURE -> "addvaultwizard.existing.notAVault.description.unsupportedStructure"; + }; + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("addvaultwizard.existing.notAVault.title") // + .setMessageKey("addvaultwizard.existing.notAVault.message") // + .setDescriptionKey(descriptionKey) // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey(BUTTON_KEY_CLOSE) // + .setOkAction(Stage::close); + } + public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) { return createDialogBuilder() // .setOwner(window) // diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 2d5c760c6..69f2fe40f 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -110,6 +110,13 @@ addvaultwizard.existing.restore=Restore… addvaultwizard.existing.chooseBtn=Choose… addvaultwizard.existing.filePickerTitle=Select Vault File addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault +addvaultwizard.existing.notAVault.title=Not a Vault +addvaultwizard.existing.notAVault.message=The selected folder is not a Cryptomator vault +addvaultwizard.existing.notAVault.description.missingDataDir=The required "d" subdirectory is missing inside the selected folder. +addvaultwizard.existing.notAVault.description.dataNotADirectory=The "d" entry inside the selected folder is not a directory. +addvaultwizard.existing.notAVault.description.missingVaultConfig=The required "vault.cryptomator" file is missing inside the selected folder . +addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied=File "vault.cryptomator" cannot be read due to insufficient access rights. +addvaultwizard.existing.notAVault.description.unsupportedStructure=The directory structure of the selected folder is not supported. ## Success addvaultwizard.success.nextStepsInstructions=Added vault "%s".\nYou need to unlock this vault to access or add contents. Alternatively you can unlock it at any later point in time. addvaultwizard.success.unlockNow=Unlock Now diff --git a/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java b/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java index 796bbff07..96c5a9dec 100644 --- a/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java +++ b/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java @@ -5,48 +5,46 @@ import org.junit.jupiter.api.io.TempDir; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; class VaultListManagerTest { @Test void testAssertIsVaultDirectoryWhenDataDirIsMissing(@TempDir Path tmpDir) { - NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + NotAVaultDirectoryException e = assertThrows(NotAVaultDirectoryException.class, () -> { VaultListManager.assertIsVaultDirectory(tmpDir); }); - assertTrue(e.getReason().contains(DATA_DIR_NAME + " directory is missing")); + assertEquals(NotAVaultDirectoryException.Reason.MISSING_DATA_DIR, e.notAVaultReason()); } @Test void testAssertIsVaultDirectoryWhenDataDirIsFile(@TempDir Path tmpDir) throws IOException { Files.createFile(tmpDir.resolve(DATA_DIR_NAME)); - NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + NotAVaultDirectoryException e = assertThrows(NotAVaultDirectoryException.class, () -> { VaultListManager.assertIsVaultDirectory(tmpDir); }); - assertTrue(e.getReason().contains(DATA_DIR_NAME + " is not a directory")); + assertEquals(NotAVaultDirectoryException.Reason.DATA_NOT_A_DIRECTORY, e.notAVaultReason()); } @Test void testAssertIsVaultDirectoryWhenVaultConfigAndMasterkeyAreMissing(@TempDir Path tmpDir) throws IOException { Files.createDirectory(tmpDir.resolve(DATA_DIR_NAME)); - NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + NotAVaultDirectoryException e = assertThrows(NotAVaultDirectoryException.class, () -> { VaultListManager.assertIsVaultDirectory(tmpDir); }); - assertTrue(e.getReason().contains(VAULTCONFIG_FILENAME + " is missing")); - assertTrue(e.getReason().contains(MASTERKEY_FILENAME + " is missing")); + assertEquals(NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG, e.notAVaultReason()); } @Test