mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
- Added settings (references #88)
- Added dependency EasyBind to UI - Using property bindings instead of listeners in lots of places of the UI now
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,13 @@
|
||||
<artifactId>commons-httpclient</artifactId>
|
||||
<version>${commons-httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guava -->
|
||||
<dependency>
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>frontend-webdav</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON -->
|
||||
<dependency>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Parent> 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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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> vault = new SimpleObjectProperty<>();
|
||||
private Optional<ChangePasswordListener> 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);
|
||||
}
|
||||
|
||||
@@ -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> vault = new SimpleObjectProperty<>();
|
||||
private Optional<InitializationListener> 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);
|
||||
}
|
||||
|
||||
@@ -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<Warning> warningsList;
|
||||
|
||||
@FXML
|
||||
private Button whitelistButton;
|
||||
|
||||
private final Application application;
|
||||
private final ObservableList<Warning> warnings = FXCollections.observableArrayList();
|
||||
private final ListChangeListener<String> unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange;
|
||||
private final ChangeListener<Boolean> stageVisibilityChangeListener = this::windowVisibilityDidChange;
|
||||
final ObjectProperty<Vault> 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<Warning> 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();
|
||||
|
||||
@@ -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> welcomeController;
|
||||
private final Lazy<InitializeController> initializeController;
|
||||
private final Lazy<UnlockController> unlockController;
|
||||
private final Provider<UnlockedController> unlockedControllerProvider;
|
||||
private final Lazy<ChangePasswordController> changePasswordController;
|
||||
private final Lazy<SettingsController> settingsController;
|
||||
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final Binding<Boolean> isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);
|
||||
private final BooleanBinding isShowingSettings;
|
||||
private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public MainController(Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController, Lazy<InitializeController> initializeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> 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> welcomeController;
|
||||
private final Lazy<InitializeController> initializeController;
|
||||
private final Lazy<UnlockController> unlockController;
|
||||
private final Provider<UnlockedController> unlockedController;
|
||||
private final Lazy<ChangePasswordController> changePasswordController;
|
||||
|
||||
@Inject
|
||||
public MainController(Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController, Lazy<InitializeController> initializeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedController, Lazy<ChangePasswordController> 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<Vault> 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<Vault> createDirecoryListCell(ListView<Vault> 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<Vault> createDirecoryListCell(ListView<Vault> 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<String> 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<Vault> getVaults() {
|
||||
return vaultList.getItems();
|
||||
}
|
||||
|
||||
public Collection<Vault> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"));
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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<UnlockListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
private final ExecutorService exec;
|
||||
private final Application app;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
||||
final ObjectProperty<Vault> 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<Character> 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<Boolean> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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> vault = new SimpleObjectProperty<>();
|
||||
private Optional<LockListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
private Timeline ioAnimation;
|
||||
|
||||
@Inject
|
||||
public UnlockedController(Provider<MacWarningsController> 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<MacWarningsController> 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<String> 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<String> 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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class SecPasswordField extends PasswordField {
|
||||
* Imagine the following example with <code>pass</code> being the password, <code>x</code> being the swipe char and <code>'</code> being the offset of the char array:
|
||||
* <ol>
|
||||
* <li>Append filling chars to the end of the password: <code>passxxxx'</code></li>
|
||||
* <li>Delete first 4 chars. Internal implementation will then copy the following chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
|
||||
* <li>Delete first 4 chars. Internal implementation will then copy subsequent chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
|
||||
* <li>Delete first 4 chars again, as we appended 4 chars in step 1: <code>'xxxxxx</code></li>
|
||||
* </ol>
|
||||
*/
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
|
||||
private final BooleanProperty unlocked = new SimpleBooleanProperty();
|
||||
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
private final AtomicReference<FileSystem> 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<MountParam, Optional<String>> 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<Boolean> unlockedProperty() {
|
||||
public BooleanProperty unlockedProperty() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
* });
|
||||
* </pre>
|
||||
*/
|
||||
public final class FXThreads {
|
||||
|
||||
private static final CallbackWhenTaskFailed DUMMY_EXCEPTION_CALLBACK = (e) -> {
|
||||
private static final Consumer<Exception> DUMMY_EXCEPTION_CALLBACK = (e) -> {
|
||||
// ignore.
|
||||
};
|
||||
|
||||
@@ -65,11 +66,11 @@ public final class FXThreads {
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @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 <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback) {
|
||||
public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, Consumer<T> 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());
|
||||
* });
|
||||
* </pre>
|
||||
@@ -92,7 +93,7 @@ public final class FXThreads {
|
||||
* @param successCallback The action to perform, when the task finished.
|
||||
* @param exceptionCallback
|
||||
*/
|
||||
public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback, CallbackWhenTaskFailed exceptionCallback) {
|
||||
public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, Consumer<T> successCallback, Consumer<Exception> 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<T> {
|
||||
void taskFinished(T result);
|
||||
}
|
||||
|
||||
public interface CallbackWhenTaskFailed {
|
||||
void taskFailed(Throwable t);
|
||||
}
|
||||
|
||||
public static <E> ObservableSet<E> observableSetOnMainThread(ObservableSet<E> set) {
|
||||
return new ObservableSetOnMainThread<E>(set);
|
||||
}
|
||||
|
||||
25
main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java
Normal file
25
main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java
Normal file
@@ -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 <T> ChangeListener<T> withNewValue(Consumer<T> consumer) {
|
||||
return (ObservableValue<? extends T> observable, T oldValue, T newValue) -> {
|
||||
consumer.accept(newValue);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
<MenuItem text="%main.directoryList.contextMenu.changePassword" onAction="#didClickChangePassword" />
|
||||
</items>
|
||||
</ContextMenu>
|
||||
<ContextMenu fx:id="addVaultContextMenu" onShowing="#willShowAddVaultContextMenu" onHidden="#didHideAddVaultContextMenu">
|
||||
<ContextMenu fx:id="addVaultContextMenu">
|
||||
<items>
|
||||
<MenuItem text="%main.addDirectory.contextMenu.new" onAction="#didClickCreateNewVault" />
|
||||
<MenuItem text="%main.addDirectory.contextMenu.open" onAction="#didClickAddExistingVaults" />
|
||||
@@ -49,7 +49,7 @@
|
||||
<Button text="" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
|
||||
<!-- future use: -->
|
||||
<Button text="" disable="true" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
<ToggleButton text="" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
</items>
|
||||
</ToolBar>
|
||||
</children>
|
||||
|
||||
35
main/ui/src/main/resources/fxml/settings.fxml
Normal file
35
main/ui/src/main/resources/fxml/settings.fxml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2014 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
|
||||
-->
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
|
||||
</padding>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="38.2"/>
|
||||
<ColumnConstraints percentWidth="61.8"/>
|
||||
</columnConstraints>
|
||||
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%settings.checkForUpdates.label" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="0" GridPane.columnIndex="1" fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
<Label alignment="CENTER" style="-fx-font-size: 1.5em;" text="%welcome.welcomeLabel" cacheShape="true" cache="true" />
|
||||
<VBox fx:id="checkForUpdatesContainer" prefWidth="400.0" alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
|
||||
<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
|
||||
<CheckBox fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
|
||||
<Label fx:id="checkForUpdatesStatus" text="%welcome.checkForUpdates.label.checkboxLabel" cacheShape="true" cache="true" />
|
||||
<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
|
||||
<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
</HBox>
|
||||
<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" visible="false" cacheShape="true" cache="true" />
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user