From 7436c398f8da64d4c49bf10ac3cb6a0f6b402e9e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 7 Jun 2017 13:10:50 +0200 Subject: [PATCH] fixes #511 --- .../ui/controllers/MainController.java | 11 ++-- .../ui/controllers/UnlockedController.java | 12 ++-- .../ui/controls/DirectoryListCell.java | 55 +++++++++++----- .../java/org/cryptomator/ui/model/Vault.java | 62 +++++++++++-------- 4 files changed, 88 insertions(+), 52 deletions(-) diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index 7a41bf1b7..b097e99dd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -50,6 +50,7 @@ import javafx.beans.binding.Binding; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanExpression; +import javafx.beans.binding.ObjectExpression; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -92,9 +93,9 @@ public class MainController implements ViewController { private final ObservableList vaults; private final BooleanBinding areAllVaultsLocked; private final ObjectProperty selectedVault = new SimpleObjectProperty<>(); - private final BooleanExpression isSelectedVaultUnlocked = BooleanExpression.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false)); + private final ObjectExpression selectedVaultState = ObjectExpression.objectExpression(EasyBind.select(selectedVault).selectObject(Vault::stateProperty)); private final BooleanExpression isSelectedVaultValid = BooleanExpression.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false)); - private final BooleanExpression canEditSelectedVault = selectedVault.isNotNull().and(isSelectedVaultUnlocked.not()); + private final BooleanExpression canEditSelectedVault = selectedVaultState.isEqualTo(Vault.State.LOCKED); private final MonadicBinding upgradeStrategyForSelectedVault; private final BooleanBinding isShowingSettings; private final Map unlockedVaults = new HashMap<>(); @@ -116,7 +117,7 @@ public class MainController implements ViewController { // derived bindings: this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass)); this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy); - this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault::isUnlocked)); + this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED)); EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit); autoUnlocker.unlockAllSilently(); @@ -201,7 +202,7 @@ public class MainController implements ViewController { } private void gracefulShutdown() { - vaults.filtered(Vault::isUnlocked).forEach(Vault::prepareForShutdown); + vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown); Platform.runLater(Platform::exit); } @@ -361,7 +362,7 @@ public class MainController implements ViewController { if (newValue == null) { return; } - if (newValue.isUnlocked()) { + if (newValue.getState() != Vault.State.LOCKED) { this.showUnlockedView(newValue); } else if (!newValue.doesVaultDirectoryExist()) { this.showNotFoundView(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index ff8a57028..55b76461b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -30,7 +30,7 @@ import com.google.common.util.concurrent.Runnables; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; -import javafx.beans.binding.BooleanExpression; +import javafx.beans.binding.ObjectExpression; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; @@ -64,7 +64,7 @@ public class UnlockedController implements ViewController { private final Localization localization; private final AsyncTaskService asyncTaskService; private final ObjectProperty vault = new SimpleObjectProperty<>(); - private final BooleanExpression vaultMounted = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(Vault::mountedProperty).orElse(false)); + private final ObjectExpression vaultState = ObjectExpression.objectExpression(EasyBind.select(vault).selectObject(Vault::stateProperty)); private Optional listener = Optional.empty(); private Timeline ioAnimation; @@ -103,9 +103,9 @@ public class UnlockedController implements ViewController { @Override public void initialize() { - mountVaultMenuItem.disableProperty().bind(vaultMounted); - unmountVaultMenuItem.disableProperty().bind(vaultMounted.not()); - revealVaultMenuItem.disableProperty().bind(vaultMounted.not()); + mountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.UNLOCKED).not()); // enable when unlocked + unmountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted + revealVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted EasyBind.subscribe(vault, this::vaultChanged); EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected); @@ -121,7 +121,7 @@ public class UnlockedController implements ViewController { return; } - if (newVault.getVaultSettings().mountAfterUnlock().get()) { + if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) { mountVault(newVault); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java index 2c549509c..81b0d1252 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java @@ -10,8 +10,8 @@ package org.cryptomator.ui.controls; import org.cryptomator.ui.model.Vault; import org.fxmisc.easybind.EasyBind; -import org.fxmisc.easybind.monadic.MonadicBinding; +import javafx.beans.binding.ObjectExpression; import javafx.geometry.Pos; import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContextMenu; @@ -31,9 +31,13 @@ public class DirectoryListCell extends DraggableListCell { private final Label pathText = new Label(); private final VBox vbox = new VBox(4.0, nameText, pathText); private final HBox hbox = new HBox(6.0, statusText, vbox); + private final ObjectExpression vaultState; + private ContextMenu vaultContextMenu; public DirectoryListCell() { + vaultState = ObjectExpression.objectExpression(EasyBind.select(itemProperty()).selectObject(Vault::stateProperty)); + hbox.setAlignment(Pos.CENTER_LEFT); hbox.setPrefWidth(1); vbox.setFillWidth(true); @@ -46,36 +50,57 @@ public class DirectoryListCell extends DraggableListCell { pathText.setTextOverrun(OverrunStyle.ELLIPSIS); pathText.getStyleClass().add("detail-label"); - MonadicBinding optionalItemIsUnlocked = EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty); - statusText.textProperty().bind(optionalItemIsUnlocked.map(this::getStatusIconText)); - statusText.textFillProperty().bind(EasyBind.combine(optionalItemIsUnlocked, textFillProperty(), this::getStatusIconColor)); + statusText.textProperty().bind(EasyBind.map(vaultState, this::getStatusIconText)); + statusText.textFillProperty().bind(EasyBind.combine(vaultState, textFillProperty(), this::getStatusIconColor)); statusText.setMinSize(16.0, 16.0); statusText.setAlignment(Pos.CENTER); statusText.getStyleClass().add("fontawesome"); tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath).map(p -> new Tooltip(p.toString()))); - contextMenuProperty().bind(optionalItemIsUnlocked.map(unlocked -> unlocked ? null : vaultContextMenu)); + contextMenuProperty().bind(EasyBind.map(vaultState, this::getContextMenu)); setGraphic(hbox); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } - private String getStatusIconText(Boolean unlockedOrNull) { - if (Boolean.TRUE.equals(unlockedOrNull)) { - return "\uf09c"; - } else if (Boolean.FALSE.equals(unlockedOrNull)) { - return "\uf023"; - } else { + private String getStatusIconText(Vault.State state) { + if (state == null) { return ""; } + switch (state) { + case UNLOCKED: + case MOUNTED: + case MOUNTING: + case UNMOUNTING: + return "\uf09c"; + case LOCKED: + default: + return "\uf023"; + } } - private Paint getStatusIconColor(Boolean unlockedOrNull, Paint lockedValue) { - if (Boolean.TRUE.equals(unlockedOrNull)) { - return UNLOCKED_ICON_COLOR; - } else { + private Paint getStatusIconColor(Vault.State state, Paint lockedValue) { + if (state == null) { return lockedValue; } + switch (state) { + case UNLOCKED: + case MOUNTED: + case MOUNTING: + case UNMOUNTING: + return UNLOCKED_ICON_COLOR; + case LOCKED: + default: + return lockedValue; + } + } + + private ContextMenu getContextMenu(Vault.State state) { + if (state == Vault.State.LOCKED) { + return vaultContextMenu; + } else { + return null; + } } public void setVaultContextMenu(ContextMenu contextMenu) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 0f7e89e91..311d49a9e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -20,6 +20,7 @@ import java.util.EnumSet; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.function.Predicate; import javax.inject.Inject; @@ -50,25 +51,30 @@ import org.slf4j.LoggerFactory; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.binding.Binding; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; @PerVault public class Vault { + public static final Predicate NOT_LOCKED = hasState(State.LOCKED).negate(); private static final Logger LOG = LoggerFactory.getLogger(Vault.class); private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; private final Settings settings; private final VaultSettings vaultSettings; private final WebDavServer server; - private final BooleanProperty unlocked = new SimpleBooleanProperty(); - private final BooleanProperty mounted = new SimpleBooleanProperty(); private final AtomicReference cryptoFileSystem = new AtomicReference<>(); + private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); private WebDavServletController servlet; private Mount mount; + public enum State { + LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING + }; + @Inject Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server) { this.settings = settings; @@ -121,7 +127,7 @@ public class Vault { servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); servlet.start(); Platform.runLater(() -> { - unlocked.set(true); + state.set(State.UNLOCKED); }); } catch (IOException e) { LOG.error("Unable to provide filesystem", e); @@ -138,9 +144,12 @@ public class Vault { .withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) // .build(); - mount = servlet.mount(mountParams); Platform.runLater(() -> { - mounted.set(true); + state.set(State.MOUNTING); + }); + mount = servlet.mount(mountParams); // might block this thread for a while + Platform.runLater(() -> { + state.set(State.MOUNTED); }); } @@ -153,11 +162,14 @@ public class Vault { } private synchronized void unmount(Function unmountOperationChooser) throws CommandFailedException { + Platform.runLater(() -> { + state.set(State.UNMOUNTING); + }); if (mount != null) { unmountOperationChooser.apply(mount).unmount(); } Platform.runLater(() -> { - mounted.set(false); + state.set(State.UNLOCKED); }); } @@ -174,7 +186,7 @@ public class Vault { fs.close(); } Platform.runLater(() -> { - unlocked.set(false); + state.set(State.LOCKED); }); } @@ -212,8 +224,22 @@ public class Vault { // Getter/Setter // *******************************************************************************/ + public State getState() { + return state.get(); + } + + public ReadOnlyObjectProperty stateProperty() { + return state; + } + + public static Predicate hasState(State state) { + return vault -> { + return vault.getState() == state; + }; + } + public Observable[] observables() { - return new Observable[] {unlocked, mounted}; + return new Observable[] {state}; } public VaultSettings getVaultSettings() { @@ -256,22 +282,6 @@ public class Vault { return CryptoFileSystemProvider.containsVault(getPath(), MASTERKEY_FILENAME); } - public BooleanProperty unlockedProperty() { - return unlocked; - } - - public BooleanProperty mountedProperty() { - return mounted; - } - - public boolean isUnlocked() { - return unlocked.get(); - } - - public boolean isMounted() { - return mounted.get(); - } - public long pollBytesRead() { CryptoFileSystem fs = cryptoFileSystem.get(); if (fs != null) {