From be4a39c6286aa5a87cb3500812ea821017ab6817 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 12 May 2026 09:39:07 +0200 Subject: [PATCH] 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