- 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:
Sebastian Stenzel
2016-03-01 20:47:15 +01:00
parent ca929241f2
commit 94b8726379
21 changed files with 513 additions and 508 deletions

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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;
});
}
/**

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();
}
/**

View File

@@ -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"));
// }
}

View File

@@ -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);
});
}
}
}

View File

@@ -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);

View File

@@ -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);
});
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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) -&gt; {
* myLabel.setText(bookName);
* }, (exception) -&gt; {
* } , (exception) -&gt; {
* myLabel.setText(&quot;An exception occured: &quot; + 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) -&gt; {
* myLabel.setText(bookName);
* }, (exception) -&gt; {
* } , (exception) -&gt; {
* myLabel.setText(&quot;An exception occured: &quot; + 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);
}

View 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);
};
}
}

View File

@@ -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="&#xf462;" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="false" cacheShape="true" cache="true"/>
<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
<!-- future use: -->
<Button text="&#xf43c;" disable="true" focusTraversable="false" cacheShape="true" cache="true"/>
<ToggleButton text="&#xf43c;" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="false" cacheShape="true" cache="true"/>
</items>
</ToolBar>
</children>

View 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>

View File

@@ -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" />

View File

@@ -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