mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-10-22 14:22:39 +00:00
Check the vault state right away when retrying an unlock instead of blocking the UI for up to 5 seconds
This commit is contained in:
@@ -8,7 +8,6 @@ import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import javafx.beans.value.ObservableValueBase;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@@ -98,31 +97,6 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the specified time, until the desired state is reached.
|
||||
*
|
||||
* @param desiredState what state to wait for
|
||||
* @param time the maximum time to wait
|
||||
* @param unit the time unit of the {@code time} argument
|
||||
* @return {@code false} if the waiting time detectably elapsed before reaching {@code desiredState}
|
||||
* @throws InterruptedException if the current thread is interrupted
|
||||
*/
|
||||
public boolean awaitState(Value desiredState, long time, TimeUnit unit) throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
long remaining = TimeUnit.NANOSECONDS.convert(time, unit);
|
||||
while (value.get() != desiredState) {
|
||||
if (remaining <= 0L) {
|
||||
return false;
|
||||
}
|
||||
remaining = valueChanged.awaitNanos(remaining);
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits, without a timeout, until the state differs from the specified one.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.cryptomator.common.mount.ConflictingMountServiceException;
|
||||
import org.cryptomator.common.mount.FSKitExtensionSwitchedOffException;
|
||||
import org.cryptomator.common.mount.IllegalMountPointException;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
@@ -30,7 +29,6 @@ import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.ReadOnlyFileSystemException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A multi-step task that consists of background activities as well as user interaction.
|
||||
@@ -191,17 +189,11 @@ public class UnlockWorkflow extends Task<Void> {
|
||||
}
|
||||
|
||||
private void restartUnlock() {
|
||||
try {
|
||||
var isLocked = vault.stateProperty().awaitState(VaultState.Value.LOCKED, 5, TimeUnit.SECONDS);
|
||||
if (!isLocked) {
|
||||
LOG.error("Vault did not changed to LOCKED state within 5 seconds. Aborting unlock retry.");
|
||||
} else {
|
||||
appWindows.startUnlockWorkflow(vault, owner);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Waiting for LOCKED vault state was interrupted. Aborting unlock retry.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
if (!vault.isLocked()) {
|
||||
LOG.warn("Vault '{}' is {} instead of LOCKED. Aborting unlock retry.", vault.getDisplayName(), vault.getState());
|
||||
return;
|
||||
}
|
||||
appWindows.startUnlockWorkflow(vault, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.cryptomator.common.mount.FSKitExtensionSwitchedOffException;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.WhenUnlocked;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
@@ -25,7 +24,6 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.ReadOnlyFileSystemException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class UnlockWorkflowTest {
|
||||
@@ -132,19 +130,34 @@ class UnlockWorkflowTest {
|
||||
|
||||
private void assertExtensionSwitchedOffRetryRestartsUnlockWith(Stage unlockOwner) throws Exception {
|
||||
workflow = createWorkflow(unlockOwner);
|
||||
Mockito.when(dialogs.prepareFSKitExtensionSwitchedOffDialog(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(dialogBuilder);
|
||||
runFailingWith(new FSKitExtensionSwitchedOffException(new MountFailedException("switched off")));
|
||||
ArgumentCaptor<Consumer<Stage>> okAction = ArgumentCaptor.captor();
|
||||
Mockito.verify(dialogs).prepareFSKitExtensionSwitchedOffDialog(Mockito.any(), Mockito.any(), okAction.capture());
|
||||
var okAction = failWithExtensionSwitchedOffAndCaptureRetry();
|
||||
var dialogStage = Mockito.mock(Stage.class);
|
||||
|
||||
okAction.getValue().accept(dialogStage);
|
||||
okAction.accept(dialogStage);
|
||||
|
||||
Mockito.verify(dialogStage).close();
|
||||
Mockito.verify(appWindows).startUnlockWorkflow(vault, unlockOwner);
|
||||
Assertions.assertFalse(vaultSettings.usesReadOnlyMode.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtensionSwitchedOffRetryAbortsWhenVaultIsNotLocked() throws Exception {
|
||||
var okAction = failWithExtensionSwitchedOffAndCaptureRetry();
|
||||
Mockito.when(vault.isLocked()).thenReturn(false); // e.g. an unlock from the tray menu started while the dialog was open
|
||||
|
||||
okAction.accept(Mockito.mock(Stage.class));
|
||||
|
||||
Mockito.verify(appWindows, Mockito.never()).startUnlockWorkflow(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
private Consumer<Stage> failWithExtensionSwitchedOffAndCaptureRetry() throws Exception {
|
||||
Mockito.when(dialogs.prepareFSKitExtensionSwitchedOffDialog(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(dialogBuilder);
|
||||
runFailingWith(new FSKitExtensionSwitchedOffException(new MountFailedException("switched off")));
|
||||
ArgumentCaptor<Consumer<Stage>> okAction = ArgumentCaptor.captor();
|
||||
Mockito.verify(dialogs).prepareFSKitExtensionSwitchedOffDialog(Mockito.any(), Mockito.any(), okAction.capture());
|
||||
return okAction.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOnlyRetryRestartsUnlockWithOwner() throws Exception {
|
||||
Mockito.when(dialogs.prepareRetryIfReadonlyDialog(Mockito.any(), Mockito.any())).thenReturn(dialogBuilder);
|
||||
@@ -166,9 +179,7 @@ class UnlockWorkflowTest {
|
||||
invocation.getArgument(0, Runnable.class).run();
|
||||
return null;
|
||||
});
|
||||
var vaultState = Mockito.mock(VaultState.class);
|
||||
Mockito.when(vaultState.awaitState(VaultState.Value.LOCKED, 5, TimeUnit.SECONDS)).thenReturn(true);
|
||||
Mockito.when(vault.stateProperty()).thenReturn(vaultState);
|
||||
Mockito.when(vault.isLocked()).thenReturn(true);
|
||||
Mockito.doThrow(exception).when(keyLoadingStrategy).use(Mockito.any());
|
||||
|
||||
workflow.run();
|
||||
|
||||
Reference in New Issue
Block a user