Run validation on non-fx thread

old code can still run it on fx thread
This commit is contained in:
Armin Schrenk
2026-05-12 09:39:07 +02:00
parent e63a0e8ee8
commit be4a39c628
3 changed files with 55 additions and 28 deletions

View File

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

View File

@@ -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<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService) {
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> 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<Vault> 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<Vault> 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);
}
}

View File

@@ -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<MountService> mountServices;
private final ExecutorService executor;
public ListView<Vault> vaultList;
public StackPane root;
@@ -113,7 +117,8 @@ public class VaultListController implements FxController {
RecoveryKeyComponent.Factory recoveryKeyWindow, //
VaultComponent.Factory vaultComponentFactory, //
List<MountService> 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