mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 04:01:27 +00:00
Add locking mechanism to change the vault state t
This commit is contained in:
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -43,6 +44,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
@@ -52,6 +54,8 @@ public class Vault {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||
|
||||
private final StampedLock stateLock;
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Provider<Volume> volumeProvider;
|
||||
private final StringBinding defaultMountFlags;
|
||||
@@ -93,6 +97,8 @@ public class Vault {
|
||||
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
|
||||
this.stateLock = new StampedLock();
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -141,9 +147,13 @@ public class Vault {
|
||||
volume = volumeProvider.get();
|
||||
volume.mount(fs, getEffectiveMountFlags(), throwable -> {
|
||||
destroyCryptoFileSystem();
|
||||
setState(VaultState.LOCKED); //TODO: possible race conditions of the vault state. Use Platform.runLater()?
|
||||
new Thread(() -> { //TODO: maybe use the executor service
|
||||
long stamp = stateLock.writeLock();
|
||||
setState(VaultState.LOCKED, stamp);
|
||||
stateLock.unlock(stamp);
|
||||
}).start();
|
||||
if (throwable != null) {
|
||||
LOG.warn("Unexpected unmount and lock of vault" + getDisplayName(), throwable);
|
||||
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), throwable);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -180,8 +190,24 @@ public class Vault {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
public void setState(VaultState value) {
|
||||
state.setValue(value);
|
||||
public long lockVaultState() {
|
||||
return stateLock.writeLock();
|
||||
}
|
||||
|
||||
public void unlockVaultState(long stamp) {
|
||||
stateLock.unlock(stamp);
|
||||
}
|
||||
|
||||
public void setState(VaultState value, long stamp) {
|
||||
if (stateLock.isWriteLockStamp(stamp)) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
state.setValue(value);
|
||||
} else {
|
||||
Platform.runLater(() -> state.setValue(value));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalCallerException("Stamp is not a valid write lock.");
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectProperty<Exception> lastKnownExceptionProperty() {
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
@@ -108,15 +107,18 @@ public class VaultListManager {
|
||||
VaultState previousState = vault.getState();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
long stamp = vault.lockVaultState();
|
||||
try {
|
||||
VaultState determinedState = determineVaultState(vault.getPath());
|
||||
vault.setState(determinedState);
|
||||
vault.setState(determinedState, stamp);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
vault.setState(VaultState.ERROR, stamp);
|
||||
vault.setLastKnownException(e);
|
||||
yield VaultState.ERROR;
|
||||
} finally {
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
}
|
||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||
|
||||
@@ -163,6 +163,8 @@ public class VaultService {
|
||||
private final Vault vault;
|
||||
private final boolean forced;
|
||||
|
||||
private volatile long stamp;
|
||||
|
||||
/**
|
||||
* @param vault The vault to lock
|
||||
* @param forced Whether to attempt a forced lock
|
||||
@@ -176,23 +178,32 @@ public class VaultService {
|
||||
|
||||
@Override
|
||||
protected Vault call() throws Volume.VolumeException {
|
||||
this.stamp = vault.lockVaultState();
|
||||
vault.lock(forced);
|
||||
return vault;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ public class LockWorkflow extends Task<Void> {
|
||||
private final Lazy<Scene> lockForcedScene;
|
||||
private final Lazy<Scene> lockFailedScene;
|
||||
|
||||
private volatile long stamp;
|
||||
|
||||
@Inject
|
||||
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene) {
|
||||
this.lockWindow = lockWindow;
|
||||
@@ -47,6 +49,7 @@ public class LockWorkflow extends Task<Void> {
|
||||
|
||||
@Override
|
||||
protected Void call() throws Volume.VolumeException, InterruptedException {
|
||||
this.stamp = vault.lockVaultState();
|
||||
try {
|
||||
vault.lock(false);
|
||||
} catch (Volume.VolumeException e) {
|
||||
@@ -79,19 +82,21 @@ public class LockWorkflow extends Task<Void> {
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
|
||||
vault.setState(VaultState.LOCKED);
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
LOG.warn("Failed to lock {}.", vault.getDisplayName());
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
lockWindow.setScene(lockFailedScene.get());
|
||||
lockWindow.show();
|
||||
}
|
||||
@@ -99,7 +104,8 @@ public class LockWorkflow extends Task<Void> {
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ public class MigrationRunController implements FxController {
|
||||
public void migrate() {
|
||||
LOG.info("Migrating vault {}", vault.getPath());
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
long stamp = vault.lockVaultState();
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
passwordField.setDisable(true);
|
||||
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
|
||||
Platform.runLater(() -> {
|
||||
@@ -115,10 +116,10 @@ public class MigrationRunController implements FxController {
|
||||
}).onSuccess(needsAnotherMigration -> {
|
||||
if (needsAnotherMigration) {
|
||||
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
} else {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
|
||||
vault.setState(VaultState.LOCKED);
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
passwordField.wipe();
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
@@ -127,22 +128,23 @@ public class MigrationRunController implements FxController {
|
||||
passwordField.setDisable(false);
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
|
||||
LOG.error("Underlying file system not supported.", e);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
missingCapability.set(e.getMissingCapability());
|
||||
window.setScene(capabilityErrorScene.get());
|
||||
}).onError(FileNameTooLongException.class, e -> {
|
||||
LOG.error("Migration failed because the underlying file system does not support long filenames.", e);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
||||
window.setScene(impossibleScene.get());
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Migration failed for technical reasons.", e);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
||||
}).andFinally(() -> {
|
||||
vault.unlockVaultState(stamp);
|
||||
passwordField.setDisable(false);
|
||||
progressSyncTask.cancel(true);
|
||||
}).runOnce(executor);
|
||||
|
||||
@@ -59,6 +59,8 @@ public class UnlockWorkflow extends Task<Boolean> {
|
||||
private final Lazy<Scene> invalidMountPointScene;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
|
||||
private volatile long stamp;
|
||||
|
||||
@Inject
|
||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
|
||||
this.window = window;
|
||||
@@ -87,6 +89,7 @@ public class UnlockWorkflow extends Task<Boolean> {
|
||||
@Override
|
||||
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
|
||||
try {
|
||||
this.stamp = vault.lockVaultState();
|
||||
if (attemptUnlock()) {
|
||||
handleSuccess();
|
||||
return true;
|
||||
@@ -207,22 +210,25 @@ public class UnlockWorkflow extends Task<Boolean> {
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user