diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
index e7f3d5e2d..981d2f6f3 100644
--- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
@@ -72,8 +72,6 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
String activateAppleScript = String.format("tell application \"Finder\" to activate \"%s\"", volumeIdentifier);
String ejectAppleScript = String.format("tell application \"Finder\" to if \"%s\" exists then eject \"%s\"", volumeIdentifier, volumeIdentifier);
- System.err.println("open: " + openAppleScript + "\nactivate: " + activateAppleScript + "\neject: " + ejectAppleScript);
-
this.revealCommand = new ProcessBuilder("/usr/bin/osascript", "-e", openAppleScript, "-e", activateAppleScript);
this.unmountCommand = new ProcessBuilder("/usr/bin/osascript", "-e", ejectAppleScript);
}
diff --git a/main/pom.xml b/main/pom.xml
index 5f52f26cc..3b770293b 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -170,6 +170,13 @@
commons-httpclient
${commons-httpclient.version}
+
+
+
+ org.fxmisc.easybind
+ easybind
+ 1.0.3
+
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index b04a80729..e2b0b99b0 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -50,6 +50,12 @@
org.cryptomator
frontend-webdav
+
+
+
+ org.fxmisc.easybind
+ easybind
+
diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
index 32883b5a1..bed077a24 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
@@ -70,7 +70,7 @@ public class MainApplication extends Application {
mainCtrl.initStage(primaryStage);
final ResourceBundle rb = ResourceBundle.getBundle("localization");
- primaryStage.setTitle(rb.getString("app.name"));
+ primaryStage.titleProperty().bind(mainCtrl.windowTitle());
primaryStage.setResizable(false);
primaryStage.getIcons().add(new Image(MainApplication.class.getResourceAsStream("/window_icon.png")));
primaryStage.show();
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java
index d74c64610..91de748ef 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java
@@ -11,6 +11,9 @@ package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.cryptomator.common.LazyInitializer;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
@@ -23,7 +26,7 @@ import javafx.stage.Stage;
*/
abstract class AbstractFXMLViewController implements Initializable {
- private Parent fxmlRoot;
+ private final AtomicReference fxmlRoot = new AtomicReference<>();
/**
* URL from #initialize(URL, ResourceBundle)
@@ -80,16 +83,15 @@ abstract class AbstractFXMLViewController implements Initializable {
*
* @return Parent view element.
*/
- protected final synchronized Parent loadFxml() {
- if (fxmlRoot == null) {
+ protected final Parent loadFxml() {
+ return LazyInitializer.initializeLazily(fxmlRoot, () -> {
final FXMLLoader loader = createFxmlLoader();
try {
- fxmlRoot = loader.load();
+ return loader.load();
} catch (IOException e) {
throw new IllegalStateException("Could not load FXML file from location: " + loader.getLocation(), e);
}
- }
- return fxmlRoot;
+ });
}
/**
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
index 7dbdb5b0b..5d9367702 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
@@ -9,6 +9,7 @@
package org.cryptomator.ui.controllers;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
@@ -17,6 +18,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
+import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
@@ -24,7 +26,9 @@ import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@@ -36,8 +40,14 @@ public class ChangePasswordController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
+ private final Application app;
+ final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
- private Vault vault;
+
+ @Inject
+ public ChangePasswordController(Application app) {
+ this.app = app;
+ }
@FXML
private SecPasswordField oldPasswordField;
@@ -57,12 +67,12 @@ public class ChangePasswordController extends AbstractFXMLViewController {
@FXML
private Hyperlink downloadsPageLink;
- private final Application app;
-
- @Inject
- public ChangePasswordController(Application app) {
- super();
- this.app = app;
+ @Override
+ public void initialize() {
+ BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
+ BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
+ BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
+ changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
}
@Override
@@ -75,24 +85,6 @@ public class ChangePasswordController extends AbstractFXMLViewController {
return ResourceBundle.getBundle("localization");
}
- @Override
- public void initialize() {
- oldPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
- newPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
- retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
- }
-
- // ****************************************
- // Password fields
- // ****************************************
-
- private void passwordFieldsDidChange(ObservableValue extends String> property, String oldValue, String newValue) {
- boolean oldPasswordIsEmpty = oldPasswordField.getText().isEmpty();
- boolean newPasswordIsEmpty = newPasswordField.getText().isEmpty();
- boolean passwordsAreEqual = newPasswordField.getText().equals(retypePasswordField.getText());
- changePasswordButton.setDisable(oldPasswordIsEmpty || newPasswordIsEmpty || !passwordsAreEqual);
- }
-
// ****************************************
// Downloads link
// ****************************************
@@ -110,31 +102,23 @@ public class ChangePasswordController extends AbstractFXMLViewController {
private void didClickChangePasswordButton(ActionEvent event) {
downloadsPageLink.setVisible(false);
try {
- vault.changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
+ vault.get().changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
listener.ifPresent(this::invokeListenerLater);
} catch (InvalidPassphraseException e) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
- newPasswordField.swipe();
- retypePasswordField.swipe();
Platform.runLater(oldPasswordField::requestFocus);
- return;
- } catch (IOException ex) {
+ } catch (UncheckedIOException | IOException ex) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
- newPasswordField.swipe();
- retypePasswordField.swipe();
- return;
- // } catch (UnsupportedVaultException e) {
- // downloadsPageLink.setVisible(true);
- // if (e.isVaultOlderThanSoftware()) {
- // messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
- // } else if (e.isSoftwareOlderThanVault()) {
- // messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
- // }
- // newPasswordField.swipe();
- // retypePasswordField.swipe();
- // return;
+ } catch (UnsupportedVaultFormatException e) {
+ downloadsPageLink.setVisible(true);
+ LOG.warn("Unable to unlock vault: " + e.getMessage());
+ if (e.isVaultOlderThanSoftware()) {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+ } else if (e.isSoftwareOlderThanVault()) {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+ }
} finally {
oldPasswordField.swipe();
newPasswordField.swipe();
@@ -144,14 +128,6 @@ public class ChangePasswordController extends AbstractFXMLViewController {
/* Getter/Setter */
- public Vault getVault() {
- return vault;
- }
-
- public void setVault(Vault vault) {
- this.vault = vault;
- }
-
public ChangePasswordListener getListener() {
return listener.orElse(null);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
index 9a256b944..4a47b9d36 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
@@ -9,6 +9,7 @@
package org.cryptomator.ui.controllers;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.util.Optional;
@@ -23,7 +24,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@@ -34,9 +37,13 @@ public class InitializeController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
- private Vault vault;
+ final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
+ @Inject
+ public InitializeController() {
+ }
+
@FXML
private SecPasswordField passwordField;
@@ -49,8 +56,11 @@ public class InitializeController extends AbstractFXMLViewController {
@FXML
private Label messageLabel;
- @Inject
- public InitializeController() {
+ @Override
+ public void initialize() {
+ BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
+ BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
+ okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
}
@Override
@@ -63,60 +73,29 @@ public class InitializeController extends AbstractFXMLViewController {
return ResourceBundle.getBundle("localization");
}
- @Override
- public void initialize() {
- passwordField.textProperty().addListener(this::passwordFieldsDidChange);
- retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
- }
-
- // ****************************************
- // Password fields
- // ****************************************
-
- private void passwordFieldsDidChange(ObservableValue extends String> property, String oldValue, String newValue) {
- boolean passwordIsEmpty = passwordField.getText().isEmpty();
- boolean passwordsAreEqual = passwordField.getText().equals(retypePasswordField.getText());
- okButton.setDisable(passwordIsEmpty || !passwordsAreEqual);
- }
-
// ****************************************
// OK button
// ****************************************
@FXML
protected void initializeVault(ActionEvent event) {
- setControlsDisabled(true);
final CharSequence passphrase = passwordField.getCharacters();
try {
- vault.create(passphrase);
+ vault.get().create(passphrase);
listener.ifPresent(this::invokeListenerLater);
} catch (FileAlreadyExistsException ex) {
messageLabel.setText(resourceBundle.getString("initialize.messageLabel.alreadyInitialized"));
- } catch (IOException ex) {
+ } catch (UncheckedIOException | IOException ex) {
LOG.error("I/O Exception", ex);
+ messageLabel.setText(resourceBundle.getString("initialize.messageLabel.initializationFailed"));
} finally {
- setControlsDisabled(false);
passwordField.swipe();
retypePasswordField.swipe();
}
}
- private void setControlsDisabled(boolean disable) {
- passwordField.setDisable(disable);
- retypePasswordField.setDisable(disable);
- okButton.setDisable(disable);
- }
-
/* Getter/Setter */
- public Vault getVault() {
- return vault;
- }
-
- public void setVault(Vault vault) {
- this.vault = vault;
- }
-
public InitializationListener getListener() {
return listener.orElse(null);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
index 6af12e497..ed2148881 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
@@ -19,8 +19,10 @@ import org.cryptomator.ui.model.Vault;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
@@ -38,33 +40,23 @@ import javafx.util.StringConverter;
public class MacWarningsController extends AbstractFXMLViewController {
- @FXML
- private ListView warningsList;
-
- @FXML
- private Button whitelistButton;
-
private final Application application;
private final ObservableList warnings = FXCollections.observableArrayList();
private final ListChangeListener unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange;
private final ChangeListener stageVisibilityChangeListener = this::windowVisibilityDidChange;
+ final ObjectProperty vault = new SimpleObjectProperty<>();
private Stage stage;
- private Vault vault;
@Inject
public MacWarningsController(Application application) {
this.application = application;
}
- @Override
- protected URL getFxmlResourceUrl() {
- return getClass().getResource("/fxml/mac_warnings.fxml");
- }
+ @FXML
+ private ListView warningsList;
- @Override
- protected ResourceBundle getFxmlResourceBundle() {
- return ResourceBundle.getBundle("localization");
- }
+ @FXML
+ private Button whitelistButton;
@Override
public void initialize() {
@@ -85,6 +77,16 @@ public class MacWarningsController extends AbstractFXMLViewController {
}));
}
+ @Override
+ protected URL getFxmlResourceUrl() {
+ return getClass().getResource("/fxml/mac_warnings.fxml");
+ }
+
+ @Override
+ protected ResourceBundle getFxmlResourceBundle() {
+ return ResourceBundle.getBundle("localization");
+ }
+
@Override
public void initStage(Stage stage) {
super.initStage(stage);
@@ -96,8 +98,8 @@ public class MacWarningsController extends AbstractFXMLViewController {
private void didClickWhitelistButton(ActionEvent event) {
warnings.filtered(w -> w.isSelected()).stream().forEach(w -> {
final String resourceToBeWhitelisted = w.getName();
- vault.getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted);
- vault.getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted);
+ vault.get().getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted);
+ vault.get().getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted);
});
warnings.removeIf(w -> w.isSelected());
}
@@ -125,12 +127,12 @@ public class MacWarningsController extends AbstractFXMLViewController {
private void windowVisibilityDidChange(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (Boolean.TRUE.equals(newValue)) {
- stage.setTitle(String.format(resourceBundle.getString("macWarnings.windowTitle"), vault.getName()));
- warnings.addAll(vault.getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
- vault.getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
+ stage.setTitle(String.format(resourceBundle.getString("macWarnings.windowTitle"), vault.get().getName()));
+ warnings.addAll(vault.get().getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
+ vault.get().getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
} else {
- vault.getNamesOfResourcesWithInvalidMac().clear();
- vault.getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener);
+ vault.get().getNamesOfResourcesWithInvalidMac().clear();
+ vault.get().getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener);
}
}
@@ -138,10 +140,6 @@ public class MacWarningsController extends AbstractFXMLViewController {
whitelistButton.setDisable(warnings.filtered(w -> w.isSelected()).isEmpty());
}
- public void setVault(Vault vault) {
- this.vault = vault;
- }
-
private class Warning {
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
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 e468c0519..85f6eb55f 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
@@ -13,10 +13,10 @@ import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.ResourceBundle;
-import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -26,13 +26,19 @@ import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.settings.Settings;
+import org.cryptomator.ui.util.Listeners;
+import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dagger.Lazy;
import javafx.application.Platform;
+import javafx.beans.binding.Binding;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
-import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -47,13 +53,42 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
-import javafx.stage.WindowEvent;
@Singleton
public class MainController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
+ private final VaultFactory vaultFactoy;
+ private final Lazy welcomeController;
+ private final Lazy initializeController;
+ private final Lazy unlockController;
+ private final Provider unlockedControllerProvider;
+ private final Lazy changePasswordController;
+ private final Lazy settingsController;
+ private final ObjectProperty activeController = new SimpleObjectProperty<>();
+ private final ObservableList vaults;
+ private final ObjectProperty selectedVault = new SimpleObjectProperty<>();
+ private final Binding isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);
+ private final BooleanBinding isShowingSettings;
+ private final Map unlockedVaults = new HashMap<>();
+
+ @Inject
+ public MainController(Settings settings, VaultFactory vaultFactoy, Lazy welcomeController, Lazy initializeController, Lazy unlockController,
+ Provider unlockedControllerProvider, Lazy changePasswordController, Lazy settingsController) {
+ this.vaultFactoy = vaultFactoy;
+ this.welcomeController = welcomeController;
+ this.initializeController = initializeController;
+ this.unlockController = unlockController;
+ this.unlockedControllerProvider = unlockedControllerProvider;
+ this.changePasswordController = changePasswordController;
+ this.settingsController = settingsController;
+ this.vaults = FXCollections.observableList(settings.getDirectories());
+
+ // derived bindings:
+ this.isShowingSettings = activeController.isEqualTo(settingsController.get());
+ }
+
private Stage stage;
@FXML
@@ -74,28 +109,24 @@ public class MainController extends AbstractFXMLViewController {
@FXML
private Button removeVaultButton;
+ @FXML
+ private ToggleButton settingsButton;
+
@FXML
private Pane contentPane;
- private final Settings settings;
- private final VaultFactory vaultFactoy;
- private final Lazy welcomeController;
- private final Lazy initializeController;
- private final Lazy unlockController;
- private final Provider unlockedController;
- private final Lazy changePasswordController;
-
- @Inject
- public MainController(Settings settings, VaultFactory vaultFactoy, Lazy welcomeController, Lazy initializeController, Lazy unlockController,
- Provider unlockedController, Lazy changePasswordController) {
- super();
- this.settings = settings;
- this.vaultFactoy = vaultFactoy;
- this.welcomeController = welcomeController;
- this.initializeController = initializeController;
- this.unlockController = unlockController;
- this.unlockedController = unlockedController;
- this.changePasswordController = changePasswordController;
+ @Override
+ public void initialize() {
+ activeController.addListener(this::activeControllerDidChange);
+ activeController.set(welcomeController.get());
+ vaultList.setItems(vaults);
+ vaultList.setCellFactory(this::createDirecoryListCell);
+ selectedVault.addListener(this::selectedVaultDidChange);
+ selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
+ addVaultContextMenu.showingProperty().addListener(Listeners.withNewValue(addVaultButton::setSelected));
+ removeVaultButton.disableProperty().bind(selectedVault.isNull());
+ isShowingSettings.addListener(Listeners.withNewValue(settingsButton::setSelected));
+ isSelectedVaultUnlocked.addListener(Listeners.withNewValue(this::selectedVaultUnlockedDidChange));
}
@Override
@@ -108,14 +139,10 @@ public class MainController extends AbstractFXMLViewController {
return ResourceBundle.getBundle("localization");
}
- @Override
- public void initialize() {
- final ObservableList items = FXCollections.observableList(settings.getDirectories());
- vaultList.setItems(items);
- vaultList.setCellFactory(this::createDirecoryListCell);
- vaultList.getSelectionModel().getSelectedItems().addListener(this::selectedVaultDidChange);
- removeVaultButton.disableProperty().bind(vaultList.getSelectionModel().selectedItemProperty().isNull());
- this.showWelcomeView();
+ private ListCell createDirecoryListCell(ListView param) {
+ final DirectoryListCell cell = new DirectoryListCell();
+ cell.setVaultContextMenu(vaultListCellContextMenu);
+ return cell;
}
@Override
@@ -133,16 +160,6 @@ public class MainController extends AbstractFXMLViewController {
}
}
- @FXML
- private void willShowAddVaultContextMenu(WindowEvent event) {
- addVaultButton.setSelected(true);
- }
-
- @FXML
- private void didHideAddVaultContextMenu(WindowEvent event) {
- addVaultButton.setSelected(false);
- }
-
@FXML
private void didClickCreateNewVault(ActionEvent event) {
final FileChooser fileChooser = new FileChooser();
@@ -201,143 +218,117 @@ public class MainController extends AbstractFXMLViewController {
}
final Vault vault = vaultFactoy.createVault(vaultPath);
- if (!vaultList.getItems().contains(vault)) {
- vaultList.getItems().add(vault);
+ if (!vaults.contains(vault)) {
+ vaults.add(vault);
}
vaultList.getSelectionModel().select(vault);
}
- private ListCell createDirecoryListCell(ListView param) {
- final DirectoryListCell cell = new DirectoryListCell();
- cell.setVaultContextMenu(vaultListCellContextMenu);
- return cell;
- }
-
- private void selectedVaultDidChange(ListChangeListener.Change extends Vault> change) {
- final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
- if (selectedVault == null) {
- stage.setTitle(resourceBundle.getString("app.name"));
- showWelcomeView();
- } else if (!Files.isDirectory(selectedVault.getPath())) {
- Platform.runLater(() -> {
- vaultList.getItems().remove(selectedVault);
- vaultList.getSelectionModel().clearSelection();
- });
- stage.setTitle(resourceBundle.getString("app.name"));
- showWelcomeView();
- } else {
- stage.setTitle(selectedVault.getName());
- showVault(selectedVault);
- }
- }
-
@FXML
private void didClickRemoveSelectedEntry(ActionEvent e) {
- final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
- vaultList.getItems().remove(selectedVault);
- vaultList.getSelectionModel().clearSelection();
+ vaults.remove(selectedVault.get());
}
@FXML
private void didClickChangePassword(ActionEvent e) {
- final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
- showChangePasswordView(selectedVault);
+ showChangePasswordView();
+ }
+
+ @FXML
+ private void didClickShowSettings(ActionEvent e) {
+ if (settingsController.get().equals(activeController.get())) {
+ activeController.set(welcomeController.get());
+ } else {
+ activeController.set(settingsController.get());
+ }
+ vaultList.getSelectionModel().clearSelection();
+ }
+
+ // ****************************************
+ // Bindings and Property Listeners
+ // ****************************************
+
+ private void activeControllerDidChange(ObservableValue extends AbstractFXMLViewController> property, AbstractFXMLViewController oldValue, AbstractFXMLViewController newValue) {
+ final Parent root = newValue.loadFxml();
+ contentPane.getChildren().clear();
+ contentPane.getChildren().add(root);
+ }
+
+ private void selectedVaultDidChange(ObservableValue extends Vault> property, Vault oldValue, Vault newValue) {
+ if (newValue == null) {
+ return;
+ }
+ if (newValue.isUnlocked()) {
+ this.showUnlockedView(newValue);
+ } else if (newValue.isValidVaultDirectory()) {
+ this.showUnlockView();
+ } else {
+ this.showInitializeView();
+ }
+ }
+
+ private void selectedVaultUnlockedDidChange(Boolean unlocked) {
+ if (unlocked == null) {
+ // no vault selected -> no-op
+ } else if (unlocked) {
+ Platform.setImplicitExit(false);
+ this.showUnlockedView(selectedVault.get());
+ } else {
+ this.showUnlockView();
+ }
+ }
+
+ public Binding windowTitle() {
+ return EasyBind.monadic(selectedVault).map(Vault::getName).orElse(resourceBundle.getString("app.name"));
}
// ****************************************
// Subcontroller for right panel
// ****************************************
- private void showVault(Vault vault) {
- if (vault.isUnlocked()) {
- this.showUnlockedView(vault);
- } else if (vault.isValidVaultDirectory()) {
- this.showUnlockView(vault);
- } else {
- this.showInitializeView(vault);
- }
- }
-
- private void showWelcomeView() {
- final Parent root = welcomeController.get().loadFxml();
- contentPane.getChildren().clear();
- contentPane.getChildren().add(root);
- }
-
- private void showInitializeView(Vault vault) {
+ private void showInitializeView() {
final InitializeController ctrl = initializeController.get();
- final Parent root = ctrl.loadFxml();
- contentPane.getChildren().clear();
- contentPane.getChildren().add(root);
- ctrl.setVault(vault);
+ ctrl.vault.bind(selectedVault);
ctrl.setListener(this::didInitialize);
+ activeController.set(ctrl);
}
public void didInitialize(InitializeController ctrl) {
- showUnlockView(ctrl.getVault());
+ showUnlockView();
}
- private void showUnlockView(Vault vault) {
+ private void showUnlockView() {
final UnlockController ctrl = unlockController.get();
- final Parent root = ctrl.loadFxml();
- contentPane.getChildren().clear();
- contentPane.getChildren().add(root);
- ctrl.setVault(vault);
- ctrl.setListener(this::didUnlock);
- }
-
- public void didUnlock(UnlockController ctrl) {
- showUnlockedView(ctrl.getVault());
- Platform.setImplicitExit(false);
+ ctrl.vault.bind(selectedVault);
+ activeController.set(ctrl);
}
private void showUnlockedView(Vault vault) {
- final UnlockedController ctrl = unlockedController.get();
- final Parent root = ctrl.loadFxml();
- contentPane.getChildren().clear();
- contentPane.getChildren().add(root);
+ final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
+ return unlockedControllerProvider.get();
+ });
ctrl.setVault(vault);
ctrl.setListener(this::didLock);
+ activeController.set(ctrl);
}
public void didLock(UnlockedController ctrl) {
- showUnlockView(ctrl.getVault());
- if (getUnlockedVaults().isEmpty()) {
+ unlockedVaults.remove(ctrl.getVault());
+ showUnlockView();
+ if (vaults.stream().anyMatch(Vault::isUnlocked)) {
Platform.setImplicitExit(true);
}
}
- private void showChangePasswordView(Vault vault) {
+ private void showChangePasswordView() {
final ChangePasswordController ctrl = changePasswordController.get();
- final Parent root = ctrl.loadFxml();
- contentPane.getChildren().clear();
- contentPane.getChildren().add(root);
- ctrl.setVault(vault);
+ ctrl.vault.bind(selectedVault);
ctrl.setListener(this::didChangePassword);
+ activeController.set(ctrl);
}
public void didChangePassword(ChangePasswordController ctrl) {
- showUnlockView(ctrl.getVault());
- }
-
- /* Convenience */
-
- public Collection getVaults() {
- return vaultList.getItems();
- }
-
- public Collection getUnlockedVaults() {
- return getVaults().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
- }
-
- /* public Getter/Setter */
-
- public Stage getStage() {
- return stage;
- }
-
- public void setStage(Stage stage) {
- this.stage = stage;
+ showUnlockView();
}
/**
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
new file mode 100644
index 000000000..fd433fc34
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2016 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
+package org.cryptomator.ui.controllers;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.cryptomator.ui.settings.Settings;
+import org.fxmisc.easybind.EasyBind;
+
+import javafx.application.Application;
+import javafx.fxml.FXML;
+import javafx.scene.control.CheckBox;
+
+@Singleton
+public class SettingsController extends AbstractFXMLViewController {
+
+ private final Application app;
+ private final Settings settings;
+
+ @Inject
+ public SettingsController(Application app, Settings settings) {
+ this.app = app;
+ this.settings = settings;
+ }
+
+ @FXML
+ private CheckBox checkForUpdatesCheckbox;
+
+ @Override
+ public void initialize() {
+ checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
+ EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
+ }
+
+ @Override
+ protected URL getFxmlResourceUrl() {
+ return getClass().getResource("/fxml/settings.fxml");
+ }
+
+ @Override
+ protected ResourceBundle getFxmlResourceBundle() {
+ return ResourceBundle.getBundle("localization");
+ }
+
+ // private boolean areUpdatesManagedExternally() {
+ // return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
+ // }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
index f9e4c2109..10692f163 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
@@ -10,10 +10,8 @@ package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Comparator;
-import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
import javax.inject.Inject;
@@ -26,12 +24,14 @@ import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
-import org.cryptomator.ui.util.FXThreads;
+import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
@@ -51,8 +51,18 @@ public class UnlockController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
- private Optional listener = Optional.empty();
- private Vault vault;
+ private final ExecutorService exec;
+ private final Application app;
+ private final WindowsDriveLetters driveLetters;
+ private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange;
+ final ObjectProperty vault = new SimpleObjectProperty<>();
+
+ @Inject
+ public UnlockController(Application app, ExecutorService exec, WindowsDriveLetters driveLetters) {
+ this.app = app;
+ this.exec = exec;
+ this.driveLetters = driveLetters;
+ }
@FXML
private SecPasswordField passwordField;
@@ -84,16 +94,22 @@ public class UnlockController extends AbstractFXMLViewController {
@FXML
private GridPane advancedOptions;
- private final ExecutorService exec;
- private final Application app;
- private final WindowsDriveLetters driveLetters;
- private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange;
+ @Override
+ public void initialize() {
+ advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
+ mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
+ mountName.textProperty().addListener(this::mountNameDidChange);
+ if (SystemUtils.IS_OS_WINDOWS) {
+ winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
+ } else {
+ winDriveLetterLabel.setVisible(false);
+ winDriveLetterLabel.setManaged(false);
+ winDriveLetter.setVisible(false);
+ winDriveLetter.setManaged(false);
+ }
+ unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
- @Inject
- public UnlockController(Application app, ExecutorService exec, WindowsDriveLetters driveLetters) {
- this.app = app;
- this.exec = exec;
- this.driveLetters = driveLetters;
+ EasyBind.subscribe(vault, this::vaultChanged);
}
@Override
@@ -106,25 +122,11 @@ public class UnlockController extends AbstractFXMLViewController {
return ResourceBundle.getBundle("localization");
}
- @Override
- public void initialize() {
- passwordField.textProperty().addListener(this::passwordFieldsDidChange);
- advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
- mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
- mountName.textProperty().addListener(this::mountNameDidChange);
- if (SystemUtils.IS_OS_WINDOWS) {
- winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
- } else {
- winDriveLetterLabel.setVisible(false);
- winDriveLetterLabel.setManaged(false);
- winDriveLetter.setVisible(false);
- winDriveLetter.setManaged(false);
+ private void vaultChanged(Vault newVault) {
+ if (newVault == null) {
+ return;
}
- }
-
- private void resetView() {
passwordField.clear();
- unlockButton.setDisable(true);
advancedOptions.setVisible(false);
advancedOptionsButton.setText(resourceBundle.getString("unlock.button.advancedOptions.show"));
progressIndicator.setVisible(false);
@@ -138,15 +140,10 @@ public class UnlockController extends AbstractFXMLViewController {
}
downloadsPageLink.setVisible(false);
messageText.setText(null);
- }
-
- // ****************************************
- // Password field
- // ****************************************
-
- private void passwordFieldsDidChange(ObservableValue extends String> property, String oldValue, String newValue) {
- boolean passwordIsEmpty = passwordField.getText().isEmpty();
- unlockButton.setDisable(passwordIsEmpty);
+ mountName.setText(newVault.getMountName());
+ if (SystemUtils.IS_OS_WINDOWS) {
+ chooseSelectedDriveLetter();
+ }
}
// ****************************************
@@ -183,14 +180,14 @@ public class UnlockController extends AbstractFXMLViewController {
}
private void mountNameDidChange(ObservableValue extends String> property, String oldValue, String newValue) {
- if (vault == null) {
+ if (vault.get() == null) {
return;
}
// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
if (newValue.isEmpty()) {
- mountName.setText(vault.getMountName());
+ mountName.setText(vault.get().getMountName());
} else {
- vault.setMountName(newValue);
+ vault.get().setMountName(newValue);
}
}
@@ -237,99 +234,19 @@ public class UnlockController extends AbstractFXMLViewController {
}
private void winDriveLetterDidChange(ObservableValue extends Character> property, Character oldValue, Character newValue) {
- if (vault == null) {
+ if (vault.get() == null) {
return;
}
- vault.setWinDriveLetter(newValue);
- }
-
- // ****************************************
- // Unlock button
- // ****************************************
-
- @FXML
- private void didClickUnlockButton(ActionEvent event) {
- setControlsDisabled(true);
- progressIndicator.setVisible(true);
- downloadsPageLink.setVisible(false);
- final CharSequence password = passwordField.getCharacters();
- try {
- vault.activateFrontend(password);
- Future futureMount = exec.submit(vault::mount);
- FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
- } catch (InvalidPassphraseException e) {
- setControlsDisabled(false);
- progressIndicator.setVisible(false);
- messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
- Platform.runLater(passwordField::requestFocus);
- } catch (UnsupportedVaultFormatException e) {
- setControlsDisabled(false);
- progressIndicator.setVisible(false);
- downloadsPageLink.setVisible(true);
- LOG.warn("Unable to unlock vault: " + e.getMessage());
- if (e.isVaultOlderThanSoftware()) {
- messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
- } else if (e.isSoftwareOlderThanVault()) {
- messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
- }
- } catch (FrontendCreationFailedException ex) {
- setControlsDisabled(false);
- progressIndicator.setVisible(false);
- messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
- LOG.error("Decryption failed for technical reasons.", ex);
- } finally {
- passwordField.swipe();
- }
- }
-
- private void setControlsDisabled(boolean disable) {
- passwordField.setDisable(disable);
- mountName.setDisable(disable);
- unlockButton.setDisable(disable);
- advancedOptionsButton.setDisable(disable);
- }
-
- private void unlockAndMountFinished(boolean mountSuccess) {
- progressIndicator.setVisible(false);
- setControlsDisabled(false);
- if (vault.isUnlocked() && !mountSuccess) {
- exec.submit(vault::deactivateFrontend);
- } else if (vault.isUnlocked() && mountSuccess) {
- exec.submit(() -> {
- try {
- vault.reveal();
- } catch (CommandFailedException e) {
- LOG.error("Failed to reveal mounted vault", e);
- }
- });
- }
- if (mountSuccess) {
- listener.ifPresent(this::invokeListenerLater);
- }
- }
-
- /* Getter/Setter */
-
- public Vault getVault() {
- return vault;
- }
-
- public void setVault(Vault vault) {
- this.resetView();
- this.vault = vault;
- this.mountName.setText(vault.getMountName());
- if (SystemUtils.IS_OS_WINDOWS) {
- chooseSelectedDriveLetter();
- }
+ vault.get().setWinDriveLetter(newValue);
}
private void chooseSelectedDriveLetter() {
assert SystemUtils.IS_OS_WINDOWS;
// if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this:
- if (driveLetters.getOccupiedDriveLetters().contains(vault.getWinDriveLetter())) {
- vault.setWinDriveLetter(null);
+ if (driveLetters.getOccupiedDriveLetters().contains(vault.get().getWinDriveLetter())) {
+ vault.get().setWinDriveLetter(null);
}
- final Character letter = vault.getWinDriveLetter();
+ final Character letter = vault.get().getWinDriveLetter();
if (letter == null) {
// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
this.winDriveLetter.getSelectionModel().selectFirst();
@@ -338,25 +255,55 @@ public class UnlockController extends AbstractFXMLViewController {
}
}
- public UnlockListener getListener() {
- return listener.orElse(null);
+ // ****************************************
+ // Unlock button
+ // ****************************************
+
+ @FXML
+ private void didClickUnlockButton(ActionEvent event) {
+ mountName.setDisable(true);
+ advancedOptionsButton.setDisable(true);
+ progressIndicator.setVisible(true);
+ downloadsPageLink.setVisible(false);
+ CharSequence password = passwordField.getCharacters();
+ exec.submit(() -> this.unlock(password));
+
}
- public void setListener(UnlockListener listener) {
- this.listener = Optional.ofNullable(listener);
- }
-
- /* callback */
-
- private void invokeListenerLater(UnlockListener listener) {
- Platform.runLater(() -> {
- listener.didUnlock(this);
- });
- }
-
- @FunctionalInterface
- interface UnlockListener {
- void didUnlock(UnlockController ctrl);
+ private void unlock(CharSequence password) {
+ try {
+ vault.get().activateFrontend(password);
+ vault.get().reveal();
+ } catch (InvalidPassphraseException e) {
+ Platform.runLater(() -> {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
+ passwordField.requestFocus();
+ });
+ } catch (UnsupportedVaultFormatException e) {
+ LOG.warn("Unable to unlock vault: " + e.getMessage());
+ Platform.runLater(() -> {
+ downloadsPageLink.setVisible(true);
+ if (e.isVaultOlderThanSoftware()) {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+ } else if (e.isSoftwareOlderThanVault()) {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+ }
+ });
+ } catch (FrontendCreationFailedException e) {
+ LOG.error("Decryption failed for technical reasons.", e);
+ Platform.runLater(() -> {
+ messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
+ });
+ } catch (CommandFailedException e) {
+ LOG.error("Failed to reveal mounted vault", e);
+ } finally {
+ Platform.runLater(() -> {
+ passwordField.swipe();
+ mountName.setDisable(false);
+ advancedOptionsButton.setDisable(false);
+ progressIndicator.setVisible(false);
+ });
+ }
}
}
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 8a6c61ee9..b1f43f32e 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
@@ -19,11 +19,14 @@ import javax.inject.Provider;
import org.cryptomator.frontend.CommandFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
+import org.fxmisc.easybind.EasyBind;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@@ -40,10 +43,22 @@ public class UnlockedController extends AbstractFXMLViewController {
private static final int IO_SAMPLING_STEPS = 100;
private static final double IO_SAMPLING_INTERVAL = 0.25;
+
+ private final Stage macWarningsWindow = new Stage();
+ private final MacWarningsController macWarningsController;
+ private final ExecutorService exec;
+ private final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
- private Vault vault;
private Timeline ioAnimation;
+ @Inject
+ public UnlockedController(Provider macWarningsControllerProvider, ExecutorService exec) {
+ this.macWarningsController = macWarningsControllerProvider.get();
+ this.exec = exec;
+
+ macWarningsController.vault.bind(this.vault);
+ }
+
@FXML
private Label messageLabel;
@@ -53,14 +68,12 @@ public class UnlockedController extends AbstractFXMLViewController {
@FXML
private NumberAxis xAxis;
- private final Stage macWarningsWindow = new Stage();
- private final MacWarningsController macWarningsController;
- private final ExecutorService exec;
+ @Override
+ public void initialize() {
+ macWarningsController.initStage(macWarningsWindow);
+ ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow);
- @Inject
- public UnlockedController(Provider macWarningsControllerProvider, ExecutorService exec) {
- this.macWarningsController = macWarningsControllerProvider.get();
- this.exec = exec;
+ EasyBind.subscribe(vault, this::vaultChanged);
}
@Override
@@ -73,17 +86,30 @@ public class UnlockedController extends AbstractFXMLViewController {
return ResourceBundle.getBundle("localization");
}
- @Override
- public void initialize() {
- macWarningsController.initStage(macWarningsWindow);
- ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow);
+ private void vaultChanged(Vault newVault) {
+ if (newVault == null) {
+ return;
+ }
+
+ // listen to MAC warnings as long as this vault is unlocked:
+ final ListChangeListener macWarningsListener = this::macWarningsDidChange;
+ newVault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener);
+ newVault.unlockedProperty().addListener((observable, oldValue, newValue) -> {
+ if (Boolean.FALSE.equals(newValue)) {
+ newVault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener);
+ }
+ });
+
+ // (re)start throughput statistics:
+ stopIoSampling();
+ startIoSampling();
}
@FXML
private void didClickRevealVault(ActionEvent event) {
exec.submit(() -> {
try {
- vault.reveal();
+ vault.get().reveal();
} catch (CommandFailedException e) {
Platform.runLater(() -> {
messageLabel.setText(resourceBundle.getString("unlocked.label.revealFailed"));
@@ -96,14 +122,14 @@ public class UnlockedController extends AbstractFXMLViewController {
private void didClickCloseVault(ActionEvent event) {
exec.submit(() -> {
try {
- vault.unmount();
+ vault.get().unmount();
} catch (CommandFailedException e) {
Platform.runLater(() -> {
messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
});
return;
}
- vault.deactivateFrontend();
+ vault.get().deactivateFrontend();
listener.ifPresent(this::invokeListenerLater);
});
}
@@ -166,7 +192,7 @@ public class UnlockedController extends AbstractFXMLViewController {
public void handle(ActionEvent event) {
step++;
- final long decBytes = vault.pollBytesRead();
+ final long decBytes = vault.get().pollBytesRead();
final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
@@ -175,7 +201,7 @@ public class UnlockedController extends AbstractFXMLViewController {
decryptedBytes.getData().remove(0);
}
- final long encBytes = vault.pollBytesWritten();
+ final long encBytes = vault.get().pollBytesWritten();
final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
@@ -192,27 +218,15 @@ public class UnlockedController extends AbstractFXMLViewController {
/* Getter/Setter */
public Vault getVault() {
- return vault;
+ return this.vault.get();
}
public void setVault(Vault vault) {
- this.vault = vault;
- macWarningsController.setVault(vault);
-
- // listen to MAC warnings as long as this vault is unlocked:
- final ListChangeListener macWarningsListener = this::macWarningsDidChange;
- vault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener);
- vault.unlockedProperty().addListener((observable, oldValue, newValue) -> {
- if (Boolean.FALSE.equals(newValue)) {
- vault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener);
- }
- });
-
- // (re)start throughput statistics:
- stopIoSampling();
- startIoSampling();
+ this.vault.set(vault);
}
+ /* callback */
+
public LockListener getListener() {
return listener.orElse(null);
}
@@ -221,8 +235,6 @@ public class UnlockedController extends AbstractFXMLViewController {
this.listener = Optional.ofNullable(listener);
}
- /* callback */
-
private void invokeListenerLater(LockListener listener) {
Platform.runLater(() -> {
listener.didLock(this);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
index 9d969682a..4e9bcc02f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
@@ -39,11 +39,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.application.Application;
import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
-import javafx.scene.control.CheckBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
@@ -61,9 +59,6 @@ public class WelcomeController extends AbstractFXMLViewController {
@FXML
private Node checkForUpdatesContainer;
- @FXML
- private CheckBox checkForUpdatesCheckbox;
-
@FXML
private Label checkForUpdatesStatus;
@@ -99,17 +94,11 @@ public class WelcomeController extends AbstractFXMLViewController {
@Override
public void initialize() {
botImageView.setImage(new Image(getClass().getResource("/bot_welcome.png").toString()));
- checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
- checkForUpdatesCheckbox.selectedProperty().addListener(this::checkForUpdatesChanged);
if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false);
checkForUpdatesContainer.setManaged(false);
- } else {
- checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
- checkForUpdatesCheckbox.selectedProperty().addListener(this::checkForUpdatesChanged);
- if (settings.isCheckForUpdatesEnabled()) {
- executor.execute(this::checkForUpdates);
- }
+ } else if (settings.isCheckForUpdatesEnabled()) {
+ executor.execute(this::checkForUpdates);
}
}
@@ -117,14 +106,6 @@ public class WelcomeController extends AbstractFXMLViewController {
// Check for updates
// ****************************************
- private void checkForUpdatesChanged(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
- assert newValue != null;
- settings.setCheckForUpdatesEnabled(newValue);
- if (newValue) {
- executor.execute(this::checkForUpdates);
- }
- }
-
private boolean areUpdatesManagedExternally() {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
}
@@ -134,7 +115,6 @@ public class WelcomeController extends AbstractFXMLViewController {
return;
}
Platform.runLater(() -> {
- checkForUpdatesCheckbox.setVisible(false);
checkForUpdatesStatus.setText(resourceBundle.getString("welcome.checkForUpdates.label.currentlyChecking"));
checkForUpdatesIndicator.setVisible(true);
});
@@ -157,8 +137,7 @@ public class WelcomeController extends AbstractFXMLViewController {
// no error handling required. Maybe next time the version check is successful.
} finally {
Platform.runLater(() -> {
- checkForUpdatesCheckbox.setVisible(true);
- checkForUpdatesStatus.setText(resourceBundle.getString("welcome.checkForUpdates.label.checkboxLabel"));
+ checkForUpdatesStatus.setText("");
checkForUpdatesIndicator.setVisible(false);
});
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java
index f551149bb..c7c6fc8fa 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java
@@ -26,7 +26,7 @@ public class SecPasswordField extends PasswordField {
* Imagine the following example with pass being the password, x being the swipe char and ' being the offset of the char array:
*
* - Append filling chars to the end of the password:
passxxxx'
- * - Delete first 4 chars. Internal implementation will then copy the following chars to the position, where the deletion occured:
xxxx'xxxx
+ * - Delete first 4 chars. Internal implementation will then copy subsequent chars to the position, where the deletion occured:
xxxx'xxxx
* - Delete first 4 chars again, as we appended 4 chars in step 1:
'xxxxxx
*
*/
@@ -37,8 +37,8 @@ public class SecPasswordField extends PasswordField {
this.getContent().insert(pwLength, new String(fillingChars), false);
this.getContent().delete(0, pwLength, true);
this.getContent().delete(0, pwLength, true);
- // previous text has now been overwritten. still we need to update the text to trigger some property bindings:
- this.setText("");
+ // previous text has now been overwritten. but we still need to update the text to trigger some property bindings:
+ this.clear();
}
}
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 a1ded7f3d..ba7ca9cc9 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
@@ -46,8 +46,9 @@ import org.cryptomator.ui.util.FXThreads;
import com.google.common.collect.ImmutableMap;
import dagger.Lazy;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -62,7 +63,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
private final DeferredCloser closer;
private final ShorteningFileSystemFactory shorteningFileSystemFactory;
private final CryptoFileSystemFactory cryptoFileSystemFactory;
- private final ObjectProperty unlocked = new SimpleObjectProperty(this, "unlocked", Boolean.FALSE);
+ private final BooleanProperty unlocked = new SimpleBooleanProperty();
private final ObservableList namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
private final Set whitelistedResourcesWithInvalidMac = new HashSet<>();
private final AtomicReference nioFileSystem = new AtomicReference<>();
@@ -118,6 +119,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
}
public synchronized void activateFrontend(CharSequence passphrase) throws FrontendCreationFailedException {
+ boolean success = false;
try {
FileSystem fs = getNioFileSystem();
FileSystem shorteningFs = shorteningFileSystemFactory.get(fs);
@@ -127,16 +129,21 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
String contextPath = StringUtils.prependIfMissing(mountName, "/");
Frontend frontend = frontendFactory.get().create(statsFs, contextPath);
filesystemFrontend = closer.closeLater(frontend);
- unlocked.set(true);
- } catch (UncheckedIOException e) {
+ frontend.mount(getMountParams());
+ success = true;
+ } catch (UncheckedIOException | CommandFailedException e) {
throw new FrontendCreationFailedException(e);
+ } finally {
+ // unlocked is a observable property and should only be changed by the FX application thread
+ final boolean finalSuccess = success;
+ Platform.runLater(() -> unlocked.set(finalSuccess));
}
}
public synchronized void deactivateFrontend() {
filesystemFrontend.close();
statsFileSystem = Optional.empty();
- unlocked.set(false);
+ Platform.runLater(() -> unlocked.set(false));
}
private Map> getMountParams() {
@@ -146,17 +153,6 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
);
}
- public Boolean mount() {
- try {
- Optionals.ifPresent(filesystemFrontend.get(), f -> {
- f.mount(getMountParams());
- });
- return true;
- } catch (CommandFailedException e) {
- return false;
- }
- }
-
public void reveal() throws CommandFailedException {
Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal);
}
@@ -213,7 +209,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
}
}
- public ObjectProperty unlockedProperty() {
+ public BooleanProperty unlockedProperty() {
return unlocked;
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java b/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java
index d9b4cefe1..089979139 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java
@@ -13,6 +13,7 @@ package org.cryptomator.ui.util;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.collections.ObservableList;
@@ -42,14 +43,14 @@ import javafx.collections.ObservableSet;
* // when done, update text label:
* runOnMainThreadWhenFinished(futureBookName, (bookName) -> {
* myLabel.setText(bookName);
- * }, (exception) -> {
+ * } , (exception) -> {
* myLabel.setText("An exception occured: " + exception.getMessage());
* });
*
*/
public final class FXThreads {
- private static final CallbackWhenTaskFailed DUMMY_EXCEPTION_CALLBACK = (e) -> {
+ private static final Consumer DUMMY_EXCEPTION_CALLBACK = (e) -> {
// ignore.
};
@@ -65,11 +66,11 @@ public final class FXThreads {
* });
*
*
- * @param executor
+ * @param executor The service to execute the background task on
* @param task The task to wait for.
* @param successCallback The action to perform, when the task finished.
*/
- public static void runOnMainThreadWhenFinished(ExecutorService executor, Future task, CallbackWhenTaskFinished successCallback) {
+ public static void runOnMainThreadWhenFinished(ExecutorService executor, Future task, Consumer successCallback) {
runOnMainThreadWhenFinished(executor, task, successCallback, DUMMY_EXCEPTION_CALLBACK);
}
@@ -82,7 +83,7 @@ public final class FXThreads {
*
* runOnMainThreadWhenFinished(futureBookNamePossiblyFailing, (bookName) -> {
* myLabel.setText(bookName);
- * }, (exception) -> {
+ * } , (exception) -> {
* myLabel.setText("An exception occured: " + exception.getMessage());
* });
*
@@ -92,7 +93,7 @@ public final class FXThreads {
* @param successCallback The action to perform, when the task finished.
* @param exceptionCallback
*/
- public static void runOnMainThreadWhenFinished(ExecutorService executor, Future task, CallbackWhenTaskFinished successCallback, CallbackWhenTaskFailed exceptionCallback) {
+ public static void runOnMainThreadWhenFinished(ExecutorService executor, Future task, Consumer successCallback, Consumer exceptionCallback) {
Objects.requireNonNull(task, "task must not be null.");
Objects.requireNonNull(successCallback, "successCallback must not be null.");
Objects.requireNonNull(exceptionCallback, "exceptionCallback must not be null.");
@@ -100,24 +101,16 @@ public final class FXThreads {
try {
final T result = task.get();
Platform.runLater(() -> {
- successCallback.taskFinished(result);
+ successCallback.accept(result);
});
} catch (Exception e) {
Platform.runLater(() -> {
- exceptionCallback.taskFailed(e);
+ exceptionCallback.accept(e);
});
}
});
}
- public interface CallbackWhenTaskFinished {
- void taskFinished(T result);
- }
-
- public interface CallbackWhenTaskFailed {
- void taskFailed(Throwable t);
- }
-
public static ObservableSet observableSetOnMainThread(ObservableSet set) {
return new ObservableSetOnMainThread(set);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java b/main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java
new file mode 100644
index 000000000..76db45b9d
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java
@@ -0,0 +1,25 @@
+package org.cryptomator.ui.util;
+
+import java.util.function.Consumer;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+
+public final class Listeners {
+
+ private Listeners() {
+ }
+
+ /**
+ * Invokes the given consumer with the new value as soon as a change is reported by an {@link ObservableValue}.
+ *
+ * @param consumer The function to call with the changed value.
+ * @return A listener that can i.e. be used in {@link ObservableValue#addListener(ChangeListener)}.
+ */
+ public static ChangeListener withNewValue(Consumer consumer) {
+ return (ObservableValue extends T> observable, T oldValue, T newValue) -> {
+ consumer.accept(newValue);
+ };
+ }
+
+}
diff --git a/main/ui/src/main/resources/fxml/main.fxml b/main/ui/src/main/resources/fxml/main.fxml
index 1dd8997d0..ca5da150c 100644
--- a/main/ui/src/main/resources/fxml/main.fxml
+++ b/main/ui/src/main/resources/fxml/main.fxml
@@ -31,7 +31,7 @@
-
+
@@ -49,7 +49,7 @@
-
+
diff --git a/main/ui/src/main/resources/fxml/settings.fxml b/main/ui/src/main/resources/fxml/settings.fxml
new file mode 100644
index 000000000..98e82c1ec
--- /dev/null
+++ b/main/ui/src/main/resources/fxml/settings.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/ui/src/main/resources/fxml/welcome.fxml b/main/ui/src/main/resources/fxml/welcome.fxml
index 5d4a9592a..3cd570b9a 100644
--- a/main/ui/src/main/resources/fxml/welcome.fxml
+++ b/main/ui/src/main/resources/fxml/welcome.fxml
@@ -29,8 +29,7 @@
-
-
+
diff --git a/main/ui/src/main/resources/localization.properties b/main/ui/src/main/resources/localization.properties
index 98503ff55..169a30c17 100644
--- a/main/ui/src/main/resources/localization.properties
+++ b/main/ui/src/main/resources/localization.properties
@@ -18,7 +18,6 @@ main.addDirectory.contextMenu.open=Add existing vault
# welcome.fxml
welcome.welcomeLabel=Welcome to Cryptomator
welcome.addButtonInstructionLabel=Start by adding a new vault
-welcome.checkForUpdates.label.checkboxLabel=Check for Updates
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
welcome.newVersionMessage=Version %s can be downloaded. This is %s.
@@ -27,6 +26,7 @@ initialize.label.password=Password
initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
initialize.messageLabel.alreadyInitialized=Vault already initialized
+initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
# unlock.fxml
unlock.label.password=Password
@@ -71,6 +71,9 @@ macWarnings.message=Cryptomator detected potentially malicious corruptions in th
macWarnings.moreInformationButton=Learn more
macWarnings.whitelistButton=Decrypt selected anyway
+# settings.fxml
+settings.checkForUpdates.label=Check for updates
+
# tray icon
tray.menu.open=Open
tray.menu.quit=Quit