mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-11 10:36:28 +00:00
alternative thread-safe vault state without requiring explicit synchronization
This commit is contained in:
@@ -163,8 +163,6 @@ 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
|
||||
@@ -178,32 +176,28 @@ 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, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
|
||||
@Singleton
|
||||
public class AppLifecycleListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
|
||||
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
|
||||
public static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
|
||||
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
|
||||
@@ -36,8 +36,6 @@ 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;
|
||||
@@ -49,7 +47,6 @@ 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) {
|
||||
@@ -82,21 +79,19 @@ public class LockWorkflow extends Task<Void> {
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
LOG.warn("Failed to lock {}.", vault.getDisplayName());
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||
lockWindow.setScene(lockFailedScene.get());
|
||||
lockWindow.show();
|
||||
}
|
||||
@@ -104,8 +99,7 @@ public class LockWorkflow extends Task<Void> {
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ public class VaultDetailController implements FxController {
|
||||
this.anyVaultSelected = vault.isNotNull();
|
||||
}
|
||||
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
||||
// TODO deduplicate w/ VaultListCellController
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
|
||||
if (state != null) {
|
||||
return switch (state) {
|
||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||
|
||||
@@ -24,7 +24,8 @@ public class VaultListCellController implements FxController {
|
||||
.map(this::getGlyphForVaultState);
|
||||
}
|
||||
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
||||
// TODO deduplicate w/ VaultDetailController
|
||||
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
|
||||
if (state != null) {
|
||||
return switch (state) {
|
||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||
|
||||
+2
-9
@@ -14,19 +14,13 @@ import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.NEEDS_MIGRATION;
|
||||
import static org.cryptomator.common.vaults.VaultState.UNLOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultListContextMenuController implements FxController {
|
||||
@@ -37,7 +31,7 @@ public class VaultListContextMenuController implements FxController {
|
||||
private final KeychainManager keychain;
|
||||
private final RemoveVaultComponent.Builder removeVault;
|
||||
private final VaultOptionsComponent.Builder vaultOptionsWindow;
|
||||
private final OptionalBinding<VaultState> selectedVaultState;
|
||||
private final OptionalBinding<VaultState.Value> selectedVaultState;
|
||||
private final Binding<Boolean> selectedVaultPassphraseStored;
|
||||
private final Binding<Boolean> selectedVaultRemovable;
|
||||
private final Binding<Boolean> selectedVaultUnlockable;
|
||||
@@ -57,7 +51,6 @@ public class VaultListContextMenuController implements FxController {
|
||||
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
||||
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||
|
||||
}
|
||||
|
||||
private boolean isPasswordStored(Vault vault) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -89,7 +90,10 @@ public class MigrationRunController implements FxController {
|
||||
if (keychain.isSupported()) {
|
||||
loadStoredPassword();
|
||||
}
|
||||
migrationButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.NEEDS_MIGRATION).or(passwordField.textProperty().isEmpty()));
|
||||
|
||||
migrationButtonDisabled.bind(ObjectExpression.objectExpression(vault.stateProperty())
|
||||
.isNotEqualTo(VaultState.Value.NEEDS_MIGRATION)
|
||||
.or(passwordField.textProperty().isEmpty()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -101,8 +105,7 @@ public class MigrationRunController implements FxController {
|
||||
public void migrate() {
|
||||
LOG.info("Migrating vault {}", vault.getPath());
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
long stamp = vault.lockVaultState();
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.NEEDS_MIGRATION, VaultState.Value.PROCESSING);
|
||||
passwordField.setDisable(true);
|
||||
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
|
||||
Platform.runLater(() -> {
|
||||
@@ -116,10 +119,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, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||
} else {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||
passwordField.wipe();
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
@@ -128,23 +131,22 @@ public class MigrationRunController implements FxController {
|
||||
passwordField.setDisable(false);
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
|
||||
LOG.error("Underlying file system not supported.", e);
|
||||
vault.setState(VaultState.NEEDS_MIGRATION, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||
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, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||
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, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
||||
}).andFinally(() -> {
|
||||
vault.unlockVaultState(stamp);
|
||||
passwordField.setDisable(false);
|
||||
progressSyncTask.cancel(true);
|
||||
}).runOnce(executor);
|
||||
|
||||
@@ -43,8 +43,8 @@ abstract class VaultStatisticsModule {
|
||||
var weakStage = new WeakReference<>(stage);
|
||||
vault.stateProperty().addListener(new ChangeListener<>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends VaultState> observable, VaultState oldValue, VaultState newValue) {
|
||||
if (newValue != VaultState.UNLOCKED) {
|
||||
public void changed(ObservableValue<? extends VaultState.Value> observable, VaultState.Value oldValue, VaultState.Value newValue) {
|
||||
if (newValue != VaultState.Value.UNLOCKED) {
|
||||
Stage stage = weakStage.get();
|
||||
if (stage != null) {
|
||||
stage.hide();
|
||||
|
||||
@@ -59,8 +59,6 @@ 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;
|
||||
@@ -89,7 +87,6 @@ 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;
|
||||
@@ -176,15 +173,12 @@ public class UnlockWorkflow extends Task<Boolean> {
|
||||
LOG.error("Unlock failed. Mountpoint doesn't exist (needs to be a folder): {}", cause.getMessage());
|
||||
}
|
||||
showInvalidMountPointScene();
|
||||
return;
|
||||
} else if (cause instanceof FileAlreadyExistsException) {
|
||||
LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage());
|
||||
showInvalidMountPointScene();
|
||||
return;
|
||||
} else if (cause instanceof DirectoryNotEmptyException) {
|
||||
LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage());
|
||||
showInvalidMountPointScene();
|
||||
return;
|
||||
} else {
|
||||
handleGenericError(impExc);
|
||||
}
|
||||
@@ -210,25 +204,22 @@ public class UnlockWorkflow extends Task<Boolean> {
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING, stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
vault.setState(VaultState.LOCKED, stamp);
|
||||
vault.unlockVaultState(stamp);
|
||||
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user