diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java index 3b4fdaa3e..ec2030fd2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java @@ -1,9 +1,5 @@ package org.cryptomator.ui.common; -import com.google.common.collect.ImmutableList; -import javafx.application.Platform; -import javafx.concurrent.ScheduledService; -import javafx.concurrent.Service; import javafx.concurrent.Task; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; @@ -13,9 +9,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; @FxApplicationScoped public class VaultService { @@ -75,36 +75,24 @@ public class VaultService { * @param forced Whether to attempt a forced lock */ public void lockAll(Collection vaults, boolean forced) { - Service service = createLockAllService(vaults, forced); - service.setOnSucceeded(evt -> LOG.info("Locked {}", service.getValue().getDisplayableName())); - service.setOnFailed(evt -> LOG.error("Failed to lock vault", evt.getSource().getException())); - service.start(); + executorService.execute(createLockAllTask(vaults, forced)); } /** - * Creates but doesn't start a lock-all service that can be run on a background thread. + * Creates but doesn't start a lock-all task. * * @param vaults The list of vaults to be locked * @param forced Whether to attempt a forced lock - * @return Service that tries to lock all given vaults and cancels itself automatically when done + * @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask */ - public Service createLockAllService(Collection vaults, boolean forced) { - Iterator iter = ImmutableList.copyOf(vaults).iterator(); - ScheduledService service = new ScheduledService<>() { - - @Override - protected Task createTask() { - assert Platform.isFxApplicationThread(); - if (iter.hasNext()) { - return new LockVaultTask(iter.next(), forced); - } else { - cancel(); - return new IllegalStateTask("This task should never be executed."); - } - } - }; - service.setExecutor(executorService); - return service; + public Task> createLockAllTask(Collection vaults, boolean forced) { + List> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList()); + lockTasks.forEach(executorService::execute); + Task> task = new WaitForTasksTask(lockTasks); + String vaultNames = vaults.stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")); + task.setOnSucceeded(evt -> LOG.info("Locked {}", vaultNames)); + task.setOnFailed(evt -> LOG.error("Failed to lock vaults " + vaultNames, evt.getSource().getException())); + return task; } private static class RevealVaultTask extends Task { @@ -125,6 +113,38 @@ public class VaultService { } } + /** + * A task that waits for completion of multiple other tasks + */ + private static class WaitForTasksTask extends Task> { + + private final Collection> startedTasks; + + public WaitForTasksTask(Collection> tasks) { + this.startedTasks = List.copyOf(tasks); + } + + @Override + protected Collection call() throws Exception { + Iterator> remainingTasks = startedTasks.iterator(); + Collection completed = new ArrayList<>(); + try { + // wait for all tasks: + while (remainingTasks.hasNext()) { + Vault lockedVault = remainingTasks.next().get(); + completed.add(lockedVault); + } + } catch (ExecutionException e) { + // cancel all remaining: + while (remainingTasks.hasNext()) { + remainingTasks.next().cancel(true); + } + throw e; + } + return List.copyOf(completed); + } + } + /** * A task that locks a vault */ @@ -165,24 +185,6 @@ public class VaultService { } - /** - * A task that throws an IllegalStateException - */ - private static class IllegalStateTask extends Task { - private final String message; - - /** - * @param message The message of the IllegalStateException - */ - public IllegalStateTask(String message) { - this.message = message; - } - - @Override - protected V call() throws IllegalStateException { - throw new IllegalStateException(message); - } - } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java index 3faffca61..66dd13ade 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.quit; import javafx.collections.ObservableList; -import javafx.concurrent.Service; +import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ContentDisplay; @@ -14,7 +14,9 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.awt.desktop.QuitResponse; -import java.util.List; +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; @QuitScoped public class QuitController implements FxController { @@ -24,14 +26,16 @@ public class QuitController implements FxController { private final Stage window; private final QuitResponse response; private final ObservableList unlockedVaults; + private final ExecutorService executorService; private final VaultService vaultService; public Button lockAndQuitButton; @Inject - QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, VaultService vaultService) { + QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, ExecutorService executorService, VaultService vaultService) { this.window = window; this.response = response; this.unlockedVaults = vaults.filtered(Vault::isUnlocked); + this.executorService = executorService; this.vaultService = vaultService; } @@ -47,24 +51,23 @@ public class QuitController implements FxController { lockAndQuitButton.setDisable(true); lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT); - Service lockAllService = vaultService.createLockAllService(unlockedVaults, false); - - lockAllService.setOnSucceeded(evt -> { - LOG.info("Locked {}", lockAllService.getValue().getDisplayableName()); + Task> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false); + lockAllTask.setOnSucceeded(evt -> { + LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", "))); if (unlockedVaults.isEmpty()) { window.close(); response.performQuit(); } }); - lockAllService.setOnFailed(evt -> { - LOG.warn("Locking failed", lockAllService.getException()); + lockAllTask.setOnFailed(evt -> { + LOG.warn("Locking failed", lockAllTask.getException()); lockAndQuitButton.setDisable(false); lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY); // TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) // see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163 response.cancelQuit(); }); - lockAllService.start(); + executorService.execute(lockAllTask); } }