diff --git a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java index 973d919fc..e99cd6680 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/AutoUnlocker.java @@ -1,30 +1,85 @@ package org.cryptomator.ui.fxapp; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultListManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.collections.ObservableList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Stream; @FxApplicationScoped public class AutoUnlocker { + private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class); + private final ObservableList vaults; private final FxApplicationWindows appWindows; + private final ScheduledExecutorService scheduler; + private ScheduledFuture unlockMissingFuture; + private ScheduledFuture timeoutFuture; @Inject - public AutoUnlocker(ObservableList vaults, FxApplicationWindows appWindows) { + public AutoUnlocker(ObservableList vaults, FxApplicationWindows appWindows, ScheduledExecutorService scheduler) { this.vaults = vaults; this.appWindows = appWindows; + this.scheduler = scheduler; } - public void unlock() { - vaults.stream().filter(Vault::isLocked) // - .filter(v -> v.getVaultSettings().unlockAfterStartup().get()) // - .>reduce(CompletableFuture.completedFuture(null), // - (unlockFlow, v) -> unlockFlow.handle((voit, ex) -> appWindows.startUnlockWorkflow(v, null)).thenCompose(stage -> stage), //we don't care here about the exception, logged elsewhere - (unlockChain1, unlockChain2) -> unlockChain1.handle((voit, ex) -> unlockChain2).thenCompose(stage -> stage)); + public void tryUnlockForTimespan(int timespan, TimeUnit timeUnit) { + // Unlock all available auto unlock vaults + Predicate shouldAutoUnlock = v -> v.getVaultSettings().unlockAfterStartup().get(); + unlockSequentially(vaults.stream().filter(shouldAutoUnlock)).thenRun(() -> startUnlockMissing(timespan, timeUnit)); } + private CompletionStage unlockSequentially(Stream vaultStream) { + // this is an attempt to run all the unlock workflows sequentially, i.e. start the next workflow only after completing/failing the previous workflow. + return vaultStream.filter(Vault::isLocked).reduce(CompletableFuture.completedFuture(null), + (prevUnlock, nextVault) -> prevUnlock.thenCompose(unused -> appWindows.startUnlockWorkflow(nextVault, null)), + (prevUnlock, nextUnlock) -> nextUnlock.exceptionally(e -> null) // we don't care here about the exception, logged elsewhere + ); + } + + private void startUnlockMissing(int timespan, TimeUnit timeUnit) { + // Start a temporary service if there are missing auto unlock vaults + if (getMissingAutoUnlockVaults().findAny().isPresent()) { + LOG.info("Found MISSING vaults, starting periodic check"); + unlockMissingFuture = scheduler.scheduleWithFixedDelay(this::unlockMissing, 0, 1, TimeUnit.SECONDS); + timeoutFuture = scheduler.schedule(this::timeout, timespan, timeUnit); + } + } + + private void unlockMissing() { + List missingAutoUnlockVaults = getMissingAutoUnlockVaults().toList(); + missingAutoUnlockVaults.forEach(VaultListManager::redetermineVaultState); + unlockSequentially(missingAutoUnlockVaults.stream()).thenRun(this::stopUnlockMissing); + } + + private void stopUnlockMissing() { + // Stop checking if there are no more missing vaults + if (getMissingAutoUnlockVaults().findAny().isEmpty()) { + LOG.info("No more MISSING vaults, stopping periodic check"); + unlockMissingFuture.cancel(false); + timeoutFuture.cancel(false); + } + } + + private void timeout() { + LOG.info("MISSING vaults periodic check timed out"); + unlockMissingFuture.cancel(false); + } + + private Stream getMissingAutoUnlockVaults() { + return vaults.stream() + .filter(Vault::isMissing) + .filter(v -> v.getVaultSettings().unlockAfterStartup().get()); + } } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 3ddb7cba6..cfc7ba62b 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; +import java.util.concurrent.TimeUnit; @FxApplicationScoped public class FxApplication { @@ -68,7 +69,6 @@ public class FxApplication { }); launchEventHandler.startHandlingLaunchEvents(); - autoUnlocker.unlock(); + autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); } - } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 2b4f8e7bc..000954bd5 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -12,6 +12,7 @@ import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.preferences.SelectedPreferencesTab; import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.unlock.UnlockComponent; +import org.cryptomator.ui.unlock.UnlockWorkflow; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,7 +115,7 @@ public class FxApplicationWindows { LOG.debug("Start unlock workflow for {}", vault.getDisplayName()); return unlockWorkflowFactory.create(vault, owner).unlockWorkflow(); }, Platform::runLater) // - .thenCompose(unlockWorkflow -> CompletableFuture.runAsync(unlockWorkflow, executor)) // + .thenAcceptAsync(UnlockWorkflow::run, executor) .exceptionally(e -> { showErrorWindow(e, owner == null ? primaryStage : owner, null); return null;