From 6c925d6a1fd53714f1f4fc7a47b7a9c45b1eae54 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 11 May 2026 12:12:51 +0200 Subject: [PATCH 1/8] Throw more specific error about vault directory --- .../common/vaults/VaultListManager.java | 42 ++++++++++- .../common/vaults/VaultListManagerTest.java | 72 +++++++++++++++++++ 2 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index e73075d0d..8cbafcd37 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -36,6 +36,7 @@ import java.util.ResourceBundle; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; import static org.cryptomator.common.vaults.VaultState.Value.*; +import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; @Singleton public class VaultListManager { @@ -74,9 +75,7 @@ public class VaultListManager { public Vault add(Path pathToVault) throws IOException { Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath(); - if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) { - throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory"); - } + assertIsVaultDirectory(normalizedPathToVault); return get(normalizedPathToVault) // .orElseGet(() -> { @@ -86,6 +85,43 @@ public class VaultListManager { }); } + 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)); + } + } + + 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)) { + 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"; + } + } + private VaultSettings newVaultSettings(Path path) { VaultSettings vaultSettings = VaultSettings.withRandomId(); vaultSettings.path.set(path); diff --git a/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java b/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java new file mode 100644 index 000000000..796bbff07 --- /dev/null +++ b/src/test/java/org/cryptomator/common/vaults/VaultListManagerTest.java @@ -0,0 +1,72 @@ +package org.cryptomator.common.vaults; + +import org.junit.jupiter.api.Test; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class VaultListManagerTest { + + @Test + void testAssertIsVaultDirectoryWhenDataDirIsMissing(@TempDir Path tmpDir) { + NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + VaultListManager.assertIsVaultDirectory(tmpDir); + }); + + assertTrue(e.getReason().contains(DATA_DIR_NAME + " directory is missing")); + } + + @Test + void testAssertIsVaultDirectoryWhenDataDirIsFile(@TempDir Path tmpDir) throws IOException { + Files.createFile(tmpDir.resolve(DATA_DIR_NAME)); + + NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + VaultListManager.assertIsVaultDirectory(tmpDir); + }); + + assertTrue(e.getReason().contains(DATA_DIR_NAME + " is not a directory")); + } + + @Test + void testAssertIsVaultDirectoryWhenVaultConfigAndMasterkeyAreMissing(@TempDir Path tmpDir) throws IOException { + Files.createDirectory(tmpDir.resolve(DATA_DIR_NAME)); + + NoSuchFileException e = assertThrows(NoSuchFileException.class, () -> { + VaultListManager.assertIsVaultDirectory(tmpDir); + }); + + assertTrue(e.getReason().contains(VAULTCONFIG_FILENAME + " is missing")); + assertTrue(e.getReason().contains(MASTERKEY_FILENAME + " is missing")); + } + + @Test + void testAssertIsVaultDirectoryAcceptsModernVault(@TempDir Path tmpDir) throws IOException { + Files.createDirectory(tmpDir.resolve(DATA_DIR_NAME)); + Files.createFile(tmpDir.resolve(VAULTCONFIG_FILENAME)); + + assertDoesNotThrow(() -> { + VaultListManager.assertIsVaultDirectory(tmpDir); + }); + } + + @Test + void testAssertIsVaultDirectoryAcceptsLegacyVaultCandidate(@TempDir Path tmpDir) throws IOException { + Files.createDirectory(tmpDir.resolve(DATA_DIR_NAME)); + Files.createFile(tmpDir.resolve(MASTERKEY_FILENAME)); + + assertDoesNotThrow(() -> { + VaultListManager.assertIsVaultDirectory(tmpDir); + }); + } + +} From e63a0e8ee82b585a9af8399a72df7d33bf32b3be Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 12 May 2026 08:12:22 +0200 Subject: [PATCH 2/8] Show user improved error message --- .../vaults/NotAVaultDirectoryException.java | 32 +++++++++++ .../common/vaults/VaultListManager.java | 54 +++++++------------ .../ChooseExistingVaultController.java | 8 +++ .../org/cryptomator/ui/dialogs/Dialogs.java | 19 +++++++ src/main/resources/i18n/strings.properties | 7 +++ .../common/vaults/VaultListManagerTest.java | 16 +++--- 6 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 src/main/java/org/cryptomator/common/vaults/NotAVaultDirectoryException.java 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 From be4a39c6286aa5a87cb3500812ea821017ab6817 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 12 May 2026 09:39:07 +0200 Subject: [PATCH 3/8] Run validation on non-fx thread old code can still run it on fx thread --- .../common/vaults/VaultListManager.java | 11 ++++- .../ui/fxapp/AppLaunchEventHandler.java | 46 +++++++++++-------- .../ui/mainwindow/VaultListController.java | 26 +++++++---- 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 6fb5dbf7b..8b106c216 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; +import javafx.application.Platform; import javafx.collections.ObservableList; import java.io.IOException; import java.nio.file.Files; @@ -71,6 +72,10 @@ public class VaultListManager { return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath())); } + /** + * Safe to call from any thread: the IO work runs on the calling thread, but the + * {@code ObservableList} mutation is marshaled to the JavaFX application thread. + */ public Vault add(Path pathToVault) throws IOException { Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath(); assertIsVaultDirectory(normalizedPathToVault); @@ -78,7 +83,11 @@ public class VaultListManager { return get(normalizedPathToVault) // .orElseGet(() -> { Vault newVault = create(newVaultSettings(normalizedPathToVault)); - vaultList.add(newVault); + if (Platform.isFxApplicationThread()) { + vaultList.add(newVault); + } else { + Platform.runLater(() -> vaultList.add(newVault)); + } return newVault; }); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java index 9d0702314..4fe318202 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java @@ -1,15 +1,18 @@ package org.cryptomator.ui.fxapp; +import org.cryptomator.common.vaults.NotAVaultDirectoryException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.launcher.AppLaunchEvent; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; +import javafx.stage.Stage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; @@ -29,14 +32,18 @@ class AppLaunchEventHandler { private final FxApplicationWindows appWindows; private final VaultListManager vaultListManager; private final VaultService vaultService; + private final Stage primaryStage; + private final Dialogs dialogs; @Inject - public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) { + public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) { this.launchEventQueue = launchEventQueue; this.executorService = executorService; this.appWindows = appWindows; this.vaultListManager = vaultListManager; this.vaultService = vaultService; + this.primaryStage = primaryStage; + this.dialogs = dialogs; } public void startHandlingLaunchEvents() { @@ -58,31 +65,34 @@ class AppLaunchEventHandler { private void handleLaunchEvent(AppLaunchEvent event) { switch (event.type()) { case REVEAL_APP -> appWindows.showMainWindow(); - case OPEN_FILE -> Platform.runLater(() -> { - event.pathsToOpen().forEach(this::openPotentialVault); - }); + case OPEN_FILE -> event.pathsToOpen().forEach(this::openPotentialVault); default -> LOG.warn("Unsupported event type: {}", event.type()); } } // TODO deduplicate MainWindowController... private void openPotentialVault(Path path) { - assert Platform.isFxApplicationThread(); - try { - Path potentialVaultPath = path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? path.getParent() : path; - final Optional v = vaultListManager.get(potentialVaultPath); - if (v.isPresent()) { - if (v.get().isUnlocked()) { - vaultService.reveal(v.get()); - } else if (v.get().isLocked()) { - appWindows.startUnlockWorkflow(v.get(), null); + assert !Platform.isFxApplicationThread(); + Path potentialVaultPath = path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? path.getParent() : path; + Optional existing = vaultListManager.get(potentialVaultPath.normalize().toAbsolutePath()); + if (existing.isPresent()) { + Platform.runLater(() -> { + if (existing.get().isUnlocked()) { + vaultService.reveal(existing.get()); + } else if (existing.get().isLocked()) { + appWindows.startUnlockWorkflow(existing.get(), null); } - } else { - vaultListManager.add(potentialVaultPath); - LOG.debug("Added vault {}", potentialVaultPath); - } + }); + return; + } + try { + vaultListManager.add(potentialVaultPath); + LOG.debug("Added vault {}", potentialVaultPath); + } catch (NotAVaultDirectoryException e) { + LOG.warn("Cannot add {}: {}", potentialVaultPath, e.getMessage()); + Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(primaryStage, e.notAVaultReason()).build().showAndWait()); } catch (IOException e) { - LOG.error("Failed to add vault " + path, e); + LOG.error("Failed to add vault {}", potentialVaultPath, e); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index f3c3ccb83..46eca27a0 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.recovery.VaultPreparator; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.NotAVaultDirectoryException; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultComponent; import org.cryptomator.common.vaults.VaultListManager; @@ -23,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; @@ -55,6 +57,7 @@ import java.util.List; import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; +import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT; @@ -90,6 +93,7 @@ public class VaultListController implements FxController { private final VaultComponent.Factory vaultComponentFactory; private final RecoveryKeyComponent.Factory recoveryKeyWindow; private final List mountServices; + private final ExecutorService executor; public ListView vaultList; public StackPane root; @@ -113,7 +117,8 @@ public class VaultListController implements FxController { RecoveryKeyComponent.Factory recoveryKeyWindow, // VaultComponent.Factory vaultComponentFactory, // List mountServices, // - FxFSEventList fxFSEventList) { + FxFSEventList fxFSEventList, // + ExecutorService executor) { this.mainWindow = mainWindow; this.vaults = vaults; this.selectedVault = selectedVault; @@ -127,6 +132,7 @@ public class VaultListController implements FxController { this.recoveryKeyWindow = recoveryKeyWindow; this.vaultComponentFactory = vaultComponentFactory; this.mountServices = mountServices; + this.executor = executor; this.emptyVaultList = Bindings.isEmpty(vaults); this.unreadEvents = fxFSEventList.unreadEventsProperty(); @@ -324,15 +330,17 @@ public class VaultListController implements FxController { } private void addVault(Path pathToVault) { - try { - if (pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT)) { - vaultListManager.add(pathToVault.getParent()); - } else { - vaultListManager.add(pathToVault); + Path target = pathToVault.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? pathToVault.getParent() : pathToVault; + executor.execute(() -> { + try { + vaultListManager.add(target); + } catch (NotAVaultDirectoryException e) { + LOG.warn("Cannot add {}: {}", target, e.getMessage()); + Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(mainWindow, e.notAVaultReason()).build().showAndWait()); + } catch (IOException e) { + LOG.error("Failed to add vault {}", target, e); } - } catch (IOException e) { - LOG.debug("Not a vault: {}", pathToVault); - } + }); } @FXML From 01b30b173e2e236ed512238f143760bbe03ce4ae Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 12 May 2026 12:09:50 +0200 Subject: [PATCH 4/8] enhance message --- .../addvaultwizard/ChooseExistingVaultController.java | 2 +- src/main/java/org/cryptomator/ui/dialogs/Dialogs.java | 6 +++--- .../cryptomator/ui/fxapp/AppLaunchEventHandler.java | 2 +- .../cryptomator/ui/mainwindow/VaultListController.java | 2 +- src/main/resources/i18n/strings.properties | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 8399d6c61..56efa284b 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -94,7 +94,7 @@ public class ChooseExistingVaultController implements FxController { 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(); + dialogs.prepareNotAVaultDirectoryDialog(window, e).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 b614bed49..4933cefc7 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -140,8 +140,8 @@ public class Dialogs { .setCancelAction(Stage::close); } - public SimpleDialog.Builder prepareNotAVaultDirectoryDialog(Stage window, NotAVaultDirectoryException.Reason reason) { - String descriptionKey = switch (reason) { + public SimpleDialog.Builder prepareNotAVaultDirectoryDialog(Stage window, NotAVaultDirectoryException e) { + String descriptionKey = switch (e.notAVaultReason()) { 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"; @@ -152,7 +152,7 @@ public class Dialogs { .setOwner(window) // .setTitleKey("addvaultwizard.existing.notAVault.title") // .setMessageKey("addvaultwizard.existing.notAVault.message") // - .setDescriptionKey(descriptionKey) // + .setDescriptionKey(descriptionKey, e.path().getFileName() != null ? e.path().getFileName().toString() : e.path().toString()) // .setIcon(FontAwesome5Icon.EXCLAMATION) // .setOkButtonKey(BUTTON_KEY_CLOSE) // .setOkAction(Stage::close); diff --git a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java index 4fe318202..77c774e6b 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java @@ -90,7 +90,7 @@ class AppLaunchEventHandler { LOG.debug("Added vault {}", potentialVaultPath); } catch (NotAVaultDirectoryException e) { LOG.warn("Cannot add {}: {}", potentialVaultPath, e.getMessage()); - Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(primaryStage, e.notAVaultReason()).build().showAndWait()); + Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(primaryStage, e).build().showAndWait()); } catch (IOException e) { LOG.error("Failed to add vault {}", potentialVaultPath, e); } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 46eca27a0..902b534fe 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -336,7 +336,7 @@ public class VaultListController implements FxController { vaultListManager.add(target); } catch (NotAVaultDirectoryException e) { LOG.warn("Cannot add {}: {}", target, e.getMessage()); - Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(mainWindow, e.notAVaultReason()).build().showAndWait()); + Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(mainWindow, e).build().showAndWait()); } catch (IOException e) { LOG.error("Failed to add vault {}", target, e); } diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 69f2fe40f..ad04e102b 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -112,11 +112,11 @@ 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. +addvaultwizard.existing.notAVault.description.missingDataDir=The required "d" subdirectory is missing inside "%s". +addvaultwizard.existing.notAVault.description.dataNotADirectory=The "d" entry inside "%s" is not a directory. +addvaultwizard.existing.notAVault.description.missingVaultConfig=The required "vault.cryptomator" file is missing inside "%s". +addvaultwizard.existing.notAVault.description.vaultConfigAccessDenied=File "vault.cryptomator" inside "%s" cannot be read due to insufficient access rights. +addvaultwizard.existing.notAVault.description.unsupportedStructure=The directory structure of "%s" 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 From 85b7265b952f0ed130a04035ab1536988738012e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 2 Jun 2026 19:52:36 +0200 Subject: [PATCH 5/8] Place vault storage location check in separate functions --- .../common/vaults/VaultListManager.java | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 8b106c216..7a9a2750b 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -25,8 +25,10 @@ import javax.inject.Singleton; import javafx.application.Platform; import javafx.collections.ObservableList; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -34,7 +36,14 @@ import java.util.ResourceBundle; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; -import static org.cryptomator.common.vaults.VaultState.Value.*; +import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.ERROR; +import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; +import static org.cryptomator.common.vaults.VaultState.Value.MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION; +import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING; +import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED; +import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; @Singleton @@ -94,29 +103,36 @@ public class VaultListManager { public static void assertIsVaultDirectory(Path pathToVault) throws IOException { if (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) { - 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); - } - } - - 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); - } - } - + checkDataDir(pathToVault); + checkConfigFile(pathToVault); //if vault is legacy _and_ not readable, just say unsupported throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.UNSUPPORTED_STRUCTURE); } } + static void checkDataDir(Path pathToVault) throws NotAVaultDirectoryException { + Path dataDir = pathToVault.resolve(DATA_DIR_NAME); + if (!Files.exists(dataDir)) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_DATA_DIR); + } + if (!Files.isDirectory(dataDir)) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.DATA_NOT_A_DIRECTORY); + } + } + + static void checkConfigFile(Path pathToVault) throws NotAVaultDirectoryException { + Path vaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME); + if (!Files.exists(vaultConfig)) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG); + } + try (var ch = Files.newByteChannel(vaultConfig, StandardOpenOption.READ)){ + ch.read(ByteBuffer.allocate(1)); + } catch (IOException e) { + LOG.warn("Failed to read vault config: {}", e.getMessage()); + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.VAULT_CONFIG_ACCESS_DENIED); + } + } + private VaultSettings newVaultSettings(Path path) { VaultSettings vaultSettings = VaultSettings.withRandomId(); vaultSettings.path.set(path); From 719a91692abf3de457a36f331e2a113574b91fa9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 2 Jun 2026 20:21:16 +0200 Subject: [PATCH 6/8] cleanup --- .../java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java | 1 - .../org/cryptomator/ui/mainwindow/VaultListController.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java index 77c774e6b..ea7d47752 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AppLaunchEventHandler.java @@ -72,7 +72,6 @@ class AppLaunchEventHandler { // TODO deduplicate MainWindowController... private void openPotentialVault(Path path) { - assert !Platform.isFxApplicationThread(); Path potentialVaultPath = path.getFileName().toString().endsWith(CRYPTOMATOR_FILENAME_EXT) ? path.getParent() : path; Optional existing = vaultListManager.get(potentialVaultPath.normalize().toAbsolutePath()); if (existing.isPresent()) { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 902b534fe..2c28d0fb1 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -338,7 +338,8 @@ public class VaultListController implements FxController { LOG.warn("Cannot add {}: {}", target, e.getMessage()); Platform.runLater(() -> dialogs.prepareNotAVaultDirectoryDialog(mainWindow, e).build().showAndWait()); } catch (IOException e) { - LOG.error("Failed to add vault {}", target, e); + LOG.warn("Failed to add vault {}", target, e); + Platform.runLater(() -> appWindows.showErrorWindow(e, mainWindow, null)); } }); } From 6c62b4d293ab5977e19f32340cc0b4f650973af2 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 3 Jun 2026 10:41:23 +0200 Subject: [PATCH 7/8] prevent duplicates when adding same vault several times do checking and adding on the fx thread. --- .../common/vaults/VaultListManager.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 7a9a2750b..553015522 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -89,16 +89,15 @@ public class VaultListManager { Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath(); assertIsVaultDirectory(normalizedPathToVault); - return get(normalizedPathToVault) // - .orElseGet(() -> { - Vault newVault = create(newVaultSettings(normalizedPathToVault)); - if (Platform.isFxApplicationThread()) { - vaultList.add(newVault); - } else { - Platform.runLater(() -> vaultList.add(newVault)); - } - return newVault; - }); + return get(normalizedPathToVault).orElseGet(() -> { + Vault newVault = create(newVaultSettings(normalizedPathToVault)); + if (Platform.isFxApplicationThread()) { + addVault(newVault); + } else { + Platform.runLater(() -> addVault(newVault)); + } + return newVault; + }); } public static void assertIsVaultDirectory(Path pathToVault) throws IOException { From 64b0e63b21f9cc035d7cac1bedeccdabda42ff8b Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 3 Jun 2026 10:41:45 +0200 Subject: [PATCH 8/8] cleaner errro report for invalid vaults --- .../common/vaults/VaultListManager.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 553015522..a5c799433 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -26,7 +26,9 @@ import javafx.application.Platform; import javafx.collections.ObservableList; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.AccessDeniedException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Collection; @@ -121,14 +123,16 @@ public class VaultListManager { static void checkConfigFile(Path pathToVault) throws NotAVaultDirectoryException { Path vaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME); - if (!Files.exists(vaultConfig)) { - throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG); - } - try (var ch = Files.newByteChannel(vaultConfig, StandardOpenOption.READ)){ + + try (var ch = Files.newByteChannel(vaultConfig, StandardOpenOption.READ)) { ch.read(ByteBuffer.allocate(1)); + } catch (AccessDeniedException e) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.VAULT_CONFIG_ACCESS_DENIED); + } catch (NoSuchFileException e) { + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.MISSING_VAULT_CONFIG); } catch (IOException e) { LOG.warn("Failed to read vault config: {}", e.getMessage()); - throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.VAULT_CONFIG_ACCESS_DENIED); + throw new NotAVaultDirectoryException(pathToVault, NotAVaultDirectoryException.Reason.UNSUPPORTED_STRUCTURE); } }