major cleanup of old 1.4.x code

This commit is contained in:
Sebastian Stenzel
2019-09-18 23:10:38 +02:00
parent 2567ca50e7
commit 8bb3530928
51 changed files with 18 additions and 6468 deletions

View File

@@ -50,9 +50,7 @@ import java.util.function.Predicate;
@PerVault
public class Vault {
@Deprecated(forRemoval = true, since = "1.5.0")
public static final Predicate<Vault> NOT_LOCKED = hasState(VaultState.LOCKED).negate();
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
@@ -111,20 +109,6 @@ public class Vault {
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
@Deprecated(forRemoval = true, since = "1.5.0")
public void create(CharSequence passphrase) throws IOException {
if (!isValidVaultDirectory()) {
CryptoFileSystemProvider.initialize(getPath(), MASTERKEY_FILENAME, passphrase);
} else {
throw new FileAlreadyExistsException(getPath().toString());
}
}
@Deprecated(forRemoval = true, since = "1.5.0")
public void changePassphrase(CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
throw new NotDirectoryException("");
@@ -150,33 +134,9 @@ public class Vault {
}
}
/**
* Ejects any mounted drives and locks this vault. no-op if this vault is currently locked.
*/
@Deprecated(forRemoval = true, since = "1.5.0")
public void prepareForShutdown() {
try {
lock(false);
} catch (Volume.VolumeException e) {
if (volume.supportsForcedUnmount()) {
try {
lock(true);
} catch (Volume.VolumeException e1) {
LOG.warn("Failed to force lock vault.", e1);
}
} else {
LOG.warn("Failed to gracefully lock vault.", e);
}
}
}
public void reveal() throws Volume.VolumeException {
volume.reveal();
}
private static Predicate<Vault> hasState(VaultState state) {
return vault -> vault.getState() == state;
}
// ******************************************************************************
// Observable Properties
@@ -283,86 +243,6 @@ public class Vault {
return vaultSettings.path().getValue();
}
/**
* @deprecated use displayablePathProperty() instead
*/
@Deprecated(forRemoval = true, since = "1.5.0")
public Binding<String> displayablePath() {
Path homeDir = Paths.get(SystemUtils.USER_HOME);
return EasyBind.map(vaultSettings.path(), p -> {
if (p.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
return homePrefix + relativePath.toString();
} else {
return p.toString();
}
});
}
/**
* @return Directory name without preceeding path components and file extension
* @deprecated use nameProperty() instead
*/
@Deprecated(forRemoval = true, since = "1.5.0")
public Binding<String> name() {
return EasyBind.map(vaultSettings.path(), Path::getFileName).map(Path::toString);
}
@Deprecated(forRemoval = true, since = "1.5.0")
public boolean doesVaultDirectoryExist() {
return Files.isDirectory(getPath());
}
@Deprecated(forRemoval = true, since = "1.5.0")
public boolean isValidVaultDirectory() {
return CryptoFileSystemProvider.containsVault(getPath(), MASTERKEY_FILENAME);
}
@Deprecated(forRemoval = true, since = "1.5.0")
public long pollBytesRead() {
CryptoFileSystem fs = cryptoFileSystem.get();
if (fs != null) {
return fs.getStats().pollBytesRead();
} else {
return 0l;
}
}
@Deprecated(forRemoval = true, since = "1.5.0")
public long pollBytesWritten() {
CryptoFileSystem fs = cryptoFileSystem.get();
if (fs != null) {
return fs.getStats().pollBytesWritten();
} else {
return 0l;
}
}
@Deprecated(forRemoval = true, since = "1.5.0")
public String getCustomMountPath() {
return vaultSettings.individualMountPath().getValueSafe();
}
@Deprecated(forRemoval = true, since = "1.5.0")
public void setCustomMountPath(String mountPath) {
vaultSettings.individualMountPath().set(mountPath);
}
@Deprecated(forRemoval = true, since = "1.5.0")
public String getMountName() {
return vaultSettings.mountName().get();
}
@Deprecated(forRemoval = true, since = "1.5.0")
public void setMountName(String mountName) throws IllegalArgumentException {
if (StringUtils.isBlank(mountName)) {
throw new IllegalArgumentException("mount name is empty");
} else {
vaultSettings.mountName().set(VaultSettings.normalizeMountName(mountName));
}
}
public boolean isHavingCustomMountFlags() {
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
}
@@ -388,25 +268,6 @@ public class Vault {
vaultSettings.mountFlags().set(mountFlags);
}
@Deprecated(forRemoval = true, since = "1.5.0")
public Character getWinDriveLetter() {
if (vaultSettings.winDriveLetter().get() == null) {
return null;
} else {
return vaultSettings.winDriveLetter().get().charAt(0);
}
}
@Deprecated(forRemoval = true, since = "1.5.0")
public void setWinDriveLetter(Path root) {
if (root == null) {
vaultSettings.winDriveLetter().set(null);
} else {
char winDriveLetter = root.toString().charAt(0);
vaultSettings.winDriveLetter().set(String.valueOf(winDriveLetter));
}
}
public String getId() {
return vaultSettings.getId();
}

View File

@@ -1,216 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
* Jean-Noël Charon - implementation of github issue #56
*******************************************************************************/
package org.cryptomator.ui;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.SwingUtilities;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped
public class ExitUtil {
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
private final Stage mainWindow;
private final Localization localization;
private final Settings settings;
private final Optional<MacFunctions> macFunctions;
private TrayIcon trayIcon;
@Inject
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) {
this.mainWindow = mainWindow;
this.localization = localization;
this.settings = settings;
this.macFunctions = macFunctions;
}
public void initExitHandler(Runnable exitCommand) {
if (SystemUtils.IS_OS_LINUX) {
initMinimizeExitHandler(exitCommand);
} else {
initTrayIconExitHandler(exitCommand);
}
}
private void initMinimizeExitHandler(Runnable exitCommand) {
mainWindow.setOnCloseRequest(e -> {
if (Platform.isImplicitExit()) {
exitCommand.run();
} else {
mainWindow.setIconified(true);
e.consume();
}
});
}
private void initTrayIconExitHandler(Runnable exitCommand) {
trayIcon = createTrayIcon(exitCommand);
try {
// double clicking tray icon should open Cryptomator
if (SystemUtils.IS_OS_WINDOWS) {
trayIcon.addMouseListener(new TrayIconMouseListener());
}
SystemTray.getSystemTray().add(trayIcon);
mainWindow.setOnCloseRequest((e) -> {
if (Platform.isImplicitExit()) {
exitCommand.run();
} else {
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToAgentApplication));
mainWindow.close();
this.showTrayNotification(trayIcon);
}
});
} catch (SecurityException | AWTException ex) {
// not working? then just go ahead and close the app
mainWindow.setOnCloseRequest((ev) -> {
exitCommand.run();
});
}
}
private TrayIcon createTrayIcon(Runnable exitCommand) {
final PopupMenu popup = new PopupMenu();
final MenuItem showItem = new MenuItem(localization.getString("tray.menu.open"));
showItem.addActionListener(this::restoreFromTray);
popup.add(showItem);
final MenuItem exitItem = new MenuItem(localization.getString("tray.menu.quit"));
exitItem.addActionListener(e -> exitCommand.run());
popup.add(exitItem);
final Image image = getAppropriateTrayIconImage(true);
return new TrayIcon(image, localization.getString("app.name"), popup);
}
/**
* @return true if <code>defaults read -g AppleInterfaceStyle</code> has an exit status of <code>0</code> (i.e. _not_ returning "key not found").
*/
private boolean isMacMenuBarDarkMode() {
try {
// check for exit status only. Once there are more modes than "dark" and "default", we might need to analyze string contents..
final Process proc = Runtime.getRuntime().exec(new String[] {"defaults", "read", "-g", "AppleInterfaceStyle"});
proc.waitFor(100, TimeUnit.MILLISECONDS);
return proc.exitValue() == 0;
} catch (IOException | InterruptedException | IllegalThreadStateException ex) {
// IllegalThreadStateException thrown by proc.exitValue(), if process didn't terminate
LOG.warn("Determining MAC OS X dark mode settings failed. Assuming default (light) mode.");
return false;
}
}
private void showTrayNotification(TrayIcon trayIcon) {
int remainingTrayNotification = settings.numTrayNotifications().get();
if (remainingTrayNotification <= 0) {
return;
} else {
settings.numTrayNotifications().set(remainingTrayNotification - 1);
}
final Runnable notificationCmd;
if (SystemUtils.IS_OS_MAC_OSX) {
final String title = localization.getString("tray.infoMsg.title");
final String msg = localization.getString("tray.infoMsg.msg.osx");
final String notificationCenterAppleScript = String.format("display notification \"%s\" with title \"%s\"", msg, title);
notificationCmd = () -> {
try {
final ScriptEngineManager mgr = new ScriptEngineManager();
final ScriptEngine engine = mgr.getEngineByName("AppleScriptEngine");
if (engine != null) {
engine.eval(notificationCenterAppleScript);
} else {
Runtime.getRuntime().exec(new String[] {"/usr/bin/osascript", "-e", notificationCenterAppleScript});
}
} catch (ScriptException | IOException e) {
// ignore, user will notice the tray icon anyway.
}
};
} else {
final String title = localization.getString("tray.infoMsg.title");
final String msg = localization.getString("tray.infoMsg.msg");
notificationCmd = () -> {
trayIcon.displayMessage(title, msg, MessageType.INFO);
};
}
SwingUtilities.invokeLater(() -> {
notificationCmd.run();
});
}
private class TrayIconMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
restoreFromTray(new ActionEvent(e.getSource(), e.getID(), e.paramString()));
}
}
}
private void restoreFromTray(ActionEvent event) {
Platform.runLater(() -> {
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToForegroundApplication));
mainWindow.show();
mainWindow.requestFocus();
});
}
public void updateTrayIcon(boolean areAllVaultsLocked) {
if (trayIcon != null) {
Image image = getAppropriateTrayIconImage(areAllVaultsLocked);
trayIcon.setImage(image);
}
}
private Image getAppropriateTrayIconImage(boolean areAllVaultsLocked) {
String resourceName;
if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) {
resourceName = areAllVaultsLocked ? "/tray_icon_mac_white.png" : "/tray_icon_unlocked_mac_white.png";
} else if (SystemUtils.IS_OS_MAC_OSX) {
resourceName = areAllVaultsLocked ? "/tray_icon_mac_black.png" : "/tray_icon_unlocked_mac_black.png";
} else {
resourceName = areAllVaultsLocked ? "/tray_icon.png" : "/tray_icon_unlocked.png";
}
return Toolkit.getDefaultToolkit().getImage(getClass().getResource(resourceName));
}
}

View File

@@ -27,7 +27,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -11,11 +11,12 @@ import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,6 +29,7 @@ import java.util.ResourceBundle;
public class ChangePasswordController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final Stage window;
private final Vault vault;
@@ -82,7 +84,7 @@ public class ChangePasswordController implements FxController {
@FXML
public void finish() {
try {
vault.changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters());
LOG.info("Successful changed password for {}", vault.getDisplayableName());
window.close();
} catch (IOException e) {

View File

@@ -6,15 +6,10 @@
* Contributors:
* Jean-Noël Charon - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.util;
package org.cryptomator.ui.common;
import com.google.common.base.Strings;
import com.nulabinc.zxcvbn.Zxcvbn;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
@@ -49,36 +44,6 @@ public class PasswordStrengthUtil {
}
}
@Deprecated
public Color getStrengthColor(Number score) {
switch (score.intValue()) {
case 0:
return Color.web("#e74c3c");
case 1:
return Color.web("#e67e22");
case 2:
return Color.web("#f1c40f");
case 3:
return Color.web("#40d47e");
case 4:
return Color.web("#27ae60");
default:
return Color.web("#ffffff", 0.5);
}
}
@Deprecated
public Background getBackgroundWithStrengthColor(Number score) {
Color c = this.getStrengthColor(score);
BackgroundFill fill = new BackgroundFill(c, CornerRadii.EMPTY, Insets.EMPTY);
return new Background(fill);
}
@Deprecated
public Background getBackgroundWithStrengthColor(Number score, Number threshold) {
return score.intValue() >= threshold.intValue() ? getBackgroundWithStrengthColor(score) : getBackgroundWithStrengthColor(-1);
}
public String getStrengthDescription(Number score) {
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());

View File

@@ -1,205 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
* Jean-Noël Charon - password strength meter
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.ui.controls.SecurePasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
import java.util.Optional;
public class ChangePasswordController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final Application app;
private final PasswordStrengthUtil strengthRater;
private final Localization localization;
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // 0-4
private Optional<ChangePasswordListener> listener = Optional.empty();
private Vault vault;
@Inject
public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) {
this.app = app;
this.strengthRater = strengthRater;
this.localization = localization;
}
@FXML
private SecurePasswordField oldPasswordField;
@FXML
private SecurePasswordField newPasswordField;
@FXML
private SecurePasswordField retypePasswordField;
@FXML
private Button changePasswordButton;
@FXML
private Text messageText;
@FXML
private Hyperlink downloadsPageLink;
@FXML
private Label passwordStrengthLabel;
@FXML
private Region passwordStrengthLevel0;
@FXML
private Region passwordStrengthLevel1;
@FXML
private Region passwordStrengthLevel2;
@FXML
private Region passwordStrengthLevel3;
@FXML
private Region passwordStrengthLevel4;
@FXML
private GridPane root;
@Override
public void initialize() {
oldPasswordField.textProperty().addListener(this::passwordsChanged);
newPasswordField.textProperty().addListener(this::passwordsChanged);
retypePasswordField.textProperty().addListener(this::passwordsChanged);
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
private void passwordsChanged(@SuppressWarnings("unused") Observable observable) {
boolean oldPasswordEmpty = oldPasswordField.getCharacters().length() == 0;
boolean newPasswordEmpty = newPasswordField.getCharacters().length() == 0;
boolean passwordsEqual = newPasswordField.getCharacters().equals(retypePasswordField.getCharacters());
changePasswordButton.setDisable(oldPasswordEmpty || newPasswordEmpty || !passwordsEqual);
passwordStrength.set(strengthRater.computeRate(newPasswordField.getCharacters().toString()));
}
@Override
public Parent getRoot() {
return root;
}
@Override
public void focus() {
oldPasswordField.requestFocus();
}
void setVault(Vault vault) {
this.vault = Objects.requireNonNull(vault);
// trigger "default" change to refresh key bindings:
changePasswordButton.setDefaultButton(false);
changePasswordButton.setDefaultButton(true);
messageText.setText(null);
}
// ****************************************
// Downloads link
// ****************************************
@FXML
public void didClickDownloadsLink(ActionEvent event) {
app.getHostServices().showDocument("https://cryptomator.org/downloads/");
}
// ****************************************
// Change password button
// ****************************************
@FXML
private void didClickChangePasswordButton(ActionEvent event) {
downloadsPageLink.setVisible(false);
try {
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters());
messageText.setText(null);
listener.ifPresent(this::invokeListenerLater);
} catch (InvalidPassphraseException e) {
messageText.setText(localization.getString("changePassword.errorMessage.wrongPassword"));
Platform.runLater(oldPasswordField::requestFocus);
} catch (UncheckedIOException | IOException ex) {
messageText.setText(localization.getString("changePassword.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
} catch (UnsupportedVaultFormatException e) {
downloadsPageLink.setVisible(true);
LOG.warn("Unable to unlock vault: " + e.getMessage());
if (e.isVaultOlderThanSoftware()) {
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
} else if (e.isSoftwareOlderThanVault()) {
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
}
} finally {
oldPasswordField.swipe();
newPasswordField.swipe();
retypePasswordField.swipe();
}
}
/* Getter/Setter */
public ChangePasswordListener getListener() {
return listener.orElse(null);
}
public void setListener(ChangePasswordListener listener) {
this.listener = Optional.ofNullable(listener);
}
/* callback */
private void invokeListenerLater(ChangePasswordListener listener) {
Platform.runLater(() -> {
listener.didChangePassword();
});
}
@FunctionalInterface
interface ChangePasswordListener {
void didChangePassword();
}
}

View File

@@ -1,167 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
* Jean-Noël Charon - password strength meter
******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import org.cryptomator.ui.controls.SecurePasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.util.PasswordStrengthUtil;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.util.Objects;
import java.util.Optional;
public class InitializeController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
private final Localization localization;
private final PasswordStrengthUtil strengthRater;
private IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // strengths: 0-4
private Optional<InitializationListener> listener = Optional.empty();
private Vault vault;
@Inject
public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) {
this.localization = localization;
this.strengthRater = strengthRater;
}
@FXML
private SecurePasswordField passwordField;
@FXML
private SecurePasswordField retypePasswordField;
@FXML
private Button okButton;
@FXML
private Label messageLabel;
@FXML
private Label passwordStrengthLabel;
@FXML
private Region passwordStrengthLevel0;
@FXML
private Region passwordStrengthLevel1;
@FXML
private Region passwordStrengthLevel2;
@FXML
private Region passwordStrengthLevel3;
@FXML
private Region passwordStrengthLevel4;
@FXML
private GridPane root;
@Override
public void initialize() {
passwordField.textProperty().addListener(this::passwordsChanged);
retypePasswordField.textProperty().addListener(this::passwordsChanged);
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
private void passwordsChanged(@SuppressWarnings("unused") Observable observable) {
boolean passwordsEmpty = passwordField.getCharacters().length() == 0;
boolean passwordsEqual = passwordField.getCharacters().equals(retypePasswordField.getCharacters());
okButton.setDisable(passwordsEmpty || !passwordsEqual);
passwordStrength.set(strengthRater.computeRate(passwordField.getCharacters().toString()));
}
@Override
public Parent getRoot() {
return root;
}
@Override
public void focus() {
passwordField.requestFocus();
}
void setVault(Vault vault) {
this.vault = Objects.requireNonNull(vault);
// trigger "default" change to refresh key bindings:
okButton.setDefaultButton(false);
okButton.setDefaultButton(true);
}
// ****************************************
// OK button
// ****************************************
@FXML
protected void initializeVault(ActionEvent event) {
final CharSequence passphrase = passwordField.getCharacters();
try {
vault.create(passphrase);
listener.ifPresent(this::invokeListenerLater);
} catch (FileAlreadyExistsException ex) {
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
} catch (IOException ex) {
LOG.error("I/O Exception", ex);
messageLabel.setText(localization.getString("initialize.messageLabel.initializationFailed"));
} finally {
passwordField.swipe();
retypePasswordField.swipe();
}
}
/* Getter/Setter */
public InitializationListener getListener() {
return listener.orElse(null);
}
public void setListener(InitializationListener listener) {
this.listener = Optional.ofNullable(listener);
}
/* callback */
private void invokeListenerLater(InitializationListener listener) {
Platform.runLater(() -> {
listener.didInitialize();
});
}
@FunctionalInterface
interface InitializationListener {
void didInitialize();
}
}

View File

@@ -1,557 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
* Jean-Noël Charon - confirmation dialog on vault removal
******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Cell;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.common.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
@FxApplicationScoped
public class MainController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
private static final String ACTIVE_WINDOW_STYLE_CLASS = "active-window";
private static final String INACTIVE_WINDOW_STYLE_CLASS = "inactive-window";
private final Stage mainWindow;
private final ExitUtil exitUtil;
private final Localization localization;
private final ExecutorService executorService;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final VaultListManager vaultFactoy;
private final ViewControllerLoader viewControllerLoader;
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
private final ObservableList<Vault> vaults;
private final BooleanBinding areAllVaultsLocked;
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
private final ObjectExpression<VaultState> selectedVaultState = ObjectExpression.objectExpression(EasyBind.select(selectedVault).selectObject(Vault::stateProperty));
private final BooleanExpression isSelectedVaultValid = BooleanExpression.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
private final BooleanExpression canEditSelectedVault = selectedVaultState.isEqualTo(VaultState.LOCKED);
private final MonadicBinding<UpgradeStrategy> upgradeStrategyForSelectedVault;
private final BooleanBinding isShowingSettings;
private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
private Subscription subs = Subscription.EMPTY;
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
VaultListManager vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, ObservableList<Vault> vaults, AutoUnlocker autoUnlocker) {
this.mainWindow = mainWindow;
this.executorService = executorService;
this.launchEventQueue = launchEventQueue;
this.exitUtil = exitUtil;
this.localization = localization;
this.vaultFactoy = vaultFactoy;
this.viewControllerLoader = viewControllerLoader;
this.vaults = vaults;
// derived bindings:
this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass));
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon);
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
autoUnlocker.unlockAllSilently();
try {
Desktop.getDesktop().setPreferencesHandler(e -> {
Platform.runLater(this::toggleShowSettings);
});
} catch (UnsupportedOperationException e) {
LOG.info("Unable to setPreferencesHandler, probably not supported on this OS.");
}
}
@FXML
private ContextMenu vaultListCellContextMenu;
@FXML
private MenuItem changePasswordMenuItem;
@FXML
private ContextMenu addVaultContextMenu;
@FXML
private HBox root;
@FXML
private ListView<Vault> vaultList;
@FXML
private ToggleButton addVaultButton;
@FXML
private Button removeVaultButton;
@FXML
private ToggleButton settingsButton;
@FXML
private Pane contentPane;
@FXML
private Pane emptyListInstructions;
@Override
public void initialize() {
vaultList.setItems(vaults);
vaultList.getSelectionModel().clearSelection();
vaultList.setOnKeyReleased(this::didPressKeyOnList);
vaultList.setCellFactory(this::createDirecoryListCell);
root.setOnKeyReleased(this::didPressKeyOnRoot);
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
changePasswordMenuItem.visibleProperty().bind(isSelectedVaultValid.and(Bindings.isNull(upgradeStrategyForSelectedVault)));
subs = subs.and(EasyBind.subscribe(selectedVault, this::selectedVaultDidChange));
subs = subs.and(EasyBind.subscribe(activeController, this::activeControllerDidChange));
subs = subs.and(EasyBind.subscribe(isShowingSettings, settingsButton::setSelected));
subs = subs.and(EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected));
}
@Override
public Parent getRoot() {
return root;
}
public void initStage(Stage stage) {
stage.setScene(new Scene(getRoot()));
stage.sizeToScene();
stage.setTitle(localization.getString("app.name")); // set once before bind to avoid display bugs with Linux window managers
stage.titleProperty().bind(windowTitle());
stage.setResizable(false);
loadFont("/css/ionicons.ttf");
loadFont("/css/fontawesome-webfont.ttf");
if (SystemUtils.IS_OS_MAC_OSX) {
subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), ACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty()));
subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), INACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty().not()));
Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString());
} else if (SystemUtils.IS_OS_LINUX) {
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_512.png")));
Application.setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString());
} else if (SystemUtils.IS_OS_WINDOWS) {
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
}
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
listenToFileOpenRequests(stage);
}
private void gracefulShutdown() {
vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
mainWindow.show(); // to keep the application open
ButtonType tryAgainButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.tryAgain"));
ButtonType forceShutdownButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.forceShutdown"));
Alert gracefulShutdownDialog = DialogBuilderUtil.buildGracefulShutdownDialog(
localization.getString("main.gracefulShutdown.dialog.title"), localization.getString("main.gracefulShutdown.dialog.header"), localization.getString("main.gracefulShutdown.dialog.content"),
forceShutdownButtonType, ButtonType.CANCEL, forceShutdownButtonType, tryAgainButtonType);
Optional<ButtonType> choice = gracefulShutdownDialog.showAndWait();
choice.ifPresent(btnType -> {
if (tryAgainButtonType.equals(btnType)) {
gracefulShutdown();
} else if (forceShutdownButtonType.equals(btnType)) {
Platform.runLater(Platform::exit);
} else {
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
showUnlockedView(vaults.get(0), false); //if there are still unlocked vaults, show one of them
} else {
showUnlockView(UnlockController.State.UNLOCKING); //otherwise show any vault
}
}
});
} else {
Platform.runLater(Platform::exit);
}
}
private void loadFont(String resourcePath) {
try (InputStream in = getClass().getResourceAsStream(resourcePath)) {
Font.loadFont(in, 12.0);
} catch (IOException e) {
LOG.warn("Error loading font from path: " + resourcePath, e);
}
}
private void listenToFileOpenRequests(Stage stage) {
Tasks.create(launchEventQueue::take).onSuccess(event -> {
stage.setIconified(false);
stage.show();
stage.toFront();
stage.requestFocus();
event.getPathsToOpen().forEach(path -> addVault(path, true));
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
}
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
final DirectoryListCell cell = new DirectoryListCell();
cell.setVaultContextMenu(vaultListCellContextMenu);
cell.setOnMouseClicked(this::didClickOnListCell);
return cell;
}
// ****************************************
// UI Events
// ****************************************
@FXML
private void didClickAddVault() {
if (addVaultContextMenu.isShowing()) {
addVaultContextMenu.hide();
} else {
addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
}
}
@FXML
private void didClickCreateNewVault() {
final FileChooser fileChooser = new FileChooser();
final File file = fileChooser.showSaveDialog(mainWindow);
if (file == null) {
return;
}
try {
final Path vaultDir = file.toPath();
if (Files.exists(vaultDir)) {
try (Stream<Path> stream = Files.list(vaultDir)) {
if (stream.filter(this::isNotHidden).findAny().isPresent()) {
buildErrorDialog( //
localization.getString("main.createVault.nonEmptyDir.title"), //
localization.getString("main.createVault.nonEmptyDir.header"), //
localization.getString("main.createVault.nonEmptyDir.content"), //
ButtonType.OK).show();
return;
}
}
} else {
Files.createDirectory(vaultDir);
}
addVault(vaultDir, true);
} catch (IOException e) {
LOG.error("Unable to create vault", e);
}
}
private boolean isNotHidden(Path file) {
return !file.getFileName().toString().startsWith(".");
}
@FXML
private void didClickAddExistingVaults() {
final FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
final List<File> files = fileChooser.showOpenMultipleDialog(mainWindow);
if (files != null) {
for (final File file : files) {
addVault(file.toPath(), true);
}
}
}
/**
* adds the given directory or selects it if it is already in the list of directories.
*
* @param path to a vault directory or masterkey file
*/
public void addVault(final Path path, boolean select) {
final Path vaultPath;
if (path != null && Files.isDirectory(path)) {
vaultPath = path;
} else if (path != null && Files.isReadable(path)) {
vaultPath = path.getParent();
} else {
LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
return;
}
final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
try {
return vaultFactoy.add(vaultPath);
} catch (NoSuchFileException e) {
throw new UncheckedIOException(e);
}
});
if (!vaults.contains(vault)) {
vaults.add(vault);
}
if (select) {
vaultList.getSelectionModel().select(vault);
activeController.get().focus();
}
}
@FXML
private void didClickRemoveSelectedEntry() {
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
localization.getString("main.directoryList.remove.confirmation.title"), //
localization.getString("main.directoryList.remove.confirmation.header"), //
localization.getString("main.directoryList.remove.confirmation.content"), //
SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);
Optional<ButtonType> choice = confirmDialog.showAndWait();
if (ButtonType.OK.equals(choice.get())) {
vaults.remove(selectedVault.get());
if (vaults.isEmpty()) {
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
} else {
activeController.get().focus();
}
}
}
@FXML
private void didClickChangePassword() {
showChangePasswordView();
}
@FXML
private void didClickShowSettings() {
toggleShowSettings();
}
private void toggleShowSettings() {
if (isShowingSettings.get()) {
showWelcomeView();
} else {
showPreferencesView();
}
vaultList.getSelectionModel().clearSelection();
}
// ****************************************
// Binding Listeners
// ****************************************
private void activeControllerDidChange(ViewController newValue) {
final Parent root = newValue.getRoot();
contentPane.getChildren().clear();
contentPane.getChildren().add(root);
}
private void selectedVaultDidChange(Vault newValue) {
if (newValue == null) {
return;
}
if (newValue.getState() != VaultState.LOCKED) {
this.showUnlockedView(newValue, false);
} else if (!newValue.doesVaultDirectoryExist()) {
this.showNotFoundView();
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
this.showUpgradeView();
} else if (newValue.isValidVaultDirectory()) {
this.showUnlockView(UnlockController.State.UNLOCKING);
} else {
this.showInitializeView();
}
}
private void didPressKeyOnList(KeyEvent e) {
if (e.getCode() == KeyCode.ENTER || e.getCode() == KeyCode.SPACE) {
activeController.get().focus();
}
}
private void didPressKeyOnRoot(KeyEvent event) {
boolean triggered;
if (SystemUtils.IS_OS_MAC) {
triggered = event.isMetaDown();
} else {
triggered = event.isControlDown() && !event.isAltDown();
}
if (triggered && event.getCode().isDigitKey()) {
int digit = Integer.valueOf(event.getText());
switch (digit) {
case 0: {
vaultList.getSelectionModel().clearSelection();
showWelcomeView();
return;
}
default: {
vaultList.getSelectionModel().select(digit - 1);
activeController.get().focus();
return;
}
}
}
}
private void didClickOnListCell(MouseEvent e) {
if (MouseEvent.MOUSE_CLICKED.equals(e.getEventType()) && e.getSource() instanceof Cell && ((Cell<?>) e.getSource()).isSelected()) {
activeController.get().focus();
}
}
// ****************************************
// Public Bindings
// ****************************************
public Binding<String> windowTitle() {
return EasyBind.monadic(selectedVault).flatMap(Vault::name).orElse(localization.getString("app.name"));
}
// ****************************************
// Subcontroller for right panel
// ****************************************
private void showWelcomeView() {
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
}
private void showPreferencesView() {
activeController.set(viewControllerLoader.load("/fxml/settings.fxml"));
}
private void showNotFoundView() {
activeController.set(viewControllerLoader.load("/fxml/notfound.fxml"));
}
private void showInitializeView() {
final InitializeController ctrl = viewControllerLoader.load("/fxml/initialize.fxml");
ctrl.setVault(selectedVault.get());
ctrl.setListener(this::didInitialize);
activeController.set(ctrl);
}
public void didInitialize() {
showUnlockView(UnlockController.State.INITIALIZED);
activeController.get().focus();
}
private void showUpgradeView() {
final UpgradeController ctrl = viewControllerLoader.load("/fxml/upgrade.fxml");
ctrl.setVault(selectedVault.get());
ctrl.setListener(this::didUpgrade);
activeController.set(ctrl);
}
public void didUpgrade() {
showUnlockView(UnlockController.State.UPGRADED);
activeController.get().focus();
}
private void showUnlockView(UnlockController.State state) {
final UnlockController ctrl = viewControllerLoader.load("/fxml/unlock.fxml");
ctrl.setVault(selectedVault.get(), state);
ctrl.setListener(this::didUnlock);
activeController.set(ctrl);
}
public void didUnlock(Vault vault) {
if (vault.equals(selectedVault.getValue())) {
this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue());
}
}
private void showUnlockedView(Vault vault, boolean reveal) {
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml"));
ctrl.setVault(vault);
ctrl.setListener(this::didLock);
if (reveal) {
ctrl.revealVault(vault);
}
activeController.set(ctrl);
}
public void didLock(UnlockedController ctrl) {
unlockedVaults.remove(ctrl.getVault());
if (ctrl.getVault().getId() == selectedVault.get().getId()) {
showUnlockView(UnlockController.State.UNLOCKING);
}
activeController.get().focus();
}
private void showChangePasswordView() {
final ChangePasswordController ctrl = viewControllerLoader.load("/fxml/change_password.fxml");
ctrl.setVault(selectedVault.get());
ctrl.setListener(this::didChangePassword);
activeController.set(ctrl);
Platform.runLater(ctrl::focus);
}
public void didChangePassword() {
showUnlockView(UnlockController.State.PASSWORD_CHANGED);
activeController.get().focus();
}
}

View File

@@ -1,31 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.layout.VBox;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import javax.inject.Inject;
@FxApplicationScoped
public class NotFoundController implements ViewController {
@Inject
public NotFoundController() {
// no-op
}
@FXML
VBox root;
@Override
public Parent getRoot() {
return root;
}
}

View File

@@ -1,180 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Volume;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Optional;
@FxApplicationScoped
public class SettingsController implements ViewController {
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
private final Localization localization;
private final Settings settings;
private final Optional<String> applicationVersion;
@Inject
public SettingsController(Localization localization, Settings settings, @Named("applicationVersion") Optional<String> applicationVersion) {
this.localization = localization;
this.settings = settings;
this.applicationVersion = applicationVersion;
this.webdavSettings = new Group();
}
@FXML
private CheckBox checkForUpdatesCheckbox;
private Group webdavSettings;
@FXML
private Label portFieldLabel;
@FXML
private TextField portField;
@FXML
private Button changePortButton;
@FXML
private Label versionLabel;
@FXML
private Label prefGvfsSchemeLabel;
@FXML
private ChoiceBox<String> prefGvfsScheme;
@FXML
private ChoiceBox<VolumeImpl> volume;
@FXML
private CheckBox debugModeCheckbox;
@FXML
private VBox root;
@Override
public void initialize() {
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
//NIOADAPTER
volume.getItems().addAll(Volume.getCurrentSupportedAdapters());
volume.setValue(settings.preferredVolumeImpl().get());
volume.setConverter(new NioAdapterImplStringConverter());
volume.valueProperty().addListener(this::setVisibilityGvfsElements);
//WEBDAV
webdavSettings.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.WEBDAV));
webdavSettings.managedProperty().bind(webdavSettings.visibleProperty());
prefGvfsScheme.managedProperty().bind(webdavSettings.visibleProperty());
prefGvfsSchemeLabel.managedProperty().bind(webdavSettings.visibleProperty());
portFieldLabel.managedProperty().bind(webdavSettings.visibleProperty());
portFieldLabel.visibleProperty().bind(webdavSettings.visibleProperty());
changePortButton.managedProperty().bind(webdavSettings.visibleProperty());
portField.managedProperty().bind(webdavSettings.visibleProperty());
portField.visibleProperty().bind(webdavSettings.visibleProperty());
portField.setText(String.valueOf(settings.port().intValue()));
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
prefGvfsScheme.getItems().add("dav");
prefGvfsScheme.getItems().add("webdav");
// prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
debugModeCheckbox.setSelected(settings.debugMode().get());
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
// settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
settings.preferredVolumeImpl().bind(volume.valueProperty());
settings.debugMode().bind(debugModeCheckbox.selectedProperty());
}
@Override
public Parent getRoot() {
return root;
}
@FXML
private void changePort() {
assert isPortValid() : "Button must be disabled, if port is invalid.";
try {
int port = Integer.parseInt(portField.getText());
settings.port().set(port);
} catch (NumberFormatException e) {
throw new IllegalStateException("Button must be disabled, if port is invalid.", e);
}
}
private boolean isPortValid() {
try {
int port = Integer.parseInt(portField.getText());
return port == 0 // choose port automatically
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
} catch (NumberFormatException e) {
return false;
}
}
private void filterNumericKeyEvents(KeyEvent t) {
if (!Strings.isNullOrEmpty(t.getCharacter()) && !DIGITS_MATCHER.matchesAllOf(t.getCharacter())) {
t.consume();
}
}
private void setVisibilityGvfsElements(@SuppressWarnings("unused") Observable obs, @SuppressWarnings("unused")Object oldValue, Object newValue) {
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
}
private boolean areUpdatesManagedExternally() {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
}
private static class NioAdapterImplStringConverter extends StringConverter<VolumeImpl> {
@Override
public String toString(VolumeImpl object) {
return object.getDisplayName();
}
@Override
public VolumeImpl fromString(String string) {
return VolumeImpl.forDisplayName(string);
}
}
}

View File

@@ -1,581 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.ui.controls.SecurePasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.WindowsDriveLetters;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.common.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
public class UnlockController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
private static final CharMatcher ALPHA_NUMERIC_MATCHER = CharMatcher.inRange('a', 'z') //
.or(CharMatcher.inRange('A', 'Z')) //
.or(CharMatcher.inRange('0', '9')) //
.or(CharMatcher.is('_')) //
.precomputed();
private final Application app;
private final Stage mainWindow;
private final Localization localization;
private final WindowsDriveLetters driveLetters;
private final ChangeListener<Path> driveLetterChangeListener = this::winDriveLetterDidChange;
private final Optional<KeychainAccess> keychainAccess;
private final Settings settings;
private final ExecutorService executor;
private Vault vault;
private Optional<UnlockListener> listener = Optional.empty();
private Subscription vaultSubs = Subscription.EMPTY;
private BooleanProperty unlocking = new SimpleBooleanProperty();
@Inject
public UnlockController(Application app, @Named("mainWindow") Stage mainWindow, Localization localization, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings, ExecutorService executor) {
this.app = app;
this.mainWindow = mainWindow;
this.localization = localization;
this.driveLetters = driveLetters;
this.keychainAccess = keychainAccess;
this.settings = settings;
this.executor = executor;
}
@FXML
private SecurePasswordField passwordField;
@FXML
private Button advancedOptionsButton;
@FXML
private Button unlockButton;
@FXML
private Text messageText;
@FXML
private CheckBox savePassword;
@FXML
private TextField mountName;
@FXML
private CheckBox useCustomMountFlags;
@FXML
private TextField mountFlags;
@FXML
private CheckBox revealAfterMount;
@FXML
private CheckBox useCustomWinDriveLetter;
@FXML
private ChoiceBox<Path> winDriveLetter;
@FXML
private CheckBox useCustomMountPoint;
@FXML
private HBox customMountPoint;
@FXML
private Label customMountPointLabel;
@FXML
private Hyperlink downloadsPageLink;
@FXML
private VBox advancedOptions;
@FXML
private VBox root;
@FXML
private CheckBox unlockAfterStartup;
@FXML
private CheckBox useReadOnlyMode;
@Override
public void initialize() {
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
advancedOptions.disableProperty().bind(unlocking);
unlockButton.disableProperty().bind(unlocking.or(passwordField.textProperty().isEmpty()));
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange);
useReadOnlyMode.selectedProperty().addListener(this::useReadOnlyDidChange);
useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange);
mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not());
mountFlags.textProperty().addListener(this::mountFlagsDidChange);
savePassword.setDisable(!keychainAccess.isPresent());
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
downloadsPageLink.visibleProperty().bind(downloadsPageLink.managedProperty());
customMountPoint.visibleProperty().bind(useCustomMountPoint.selectedProperty());
customMountPoint.managedProperty().bind(useCustomMountPoint.selectedProperty());
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
winDriveLetter.disableProperty().bind(useCustomWinDriveLetter.selectedProperty().not());
if (!SystemUtils.IS_OS_WINDOWS) {
useCustomWinDriveLetter.setVisible(false);
useCustomWinDriveLetter.setManaged(false);
winDriveLetter.setVisible(false);
winDriveLetter.setManaged(false);
}
}
@Override
public Parent getRoot() {
return root;
}
@Override
public void focus() {
passwordField.requestFocus();
}
void setVault(Vault vault, State state) {
vaultSubs.unsubscribe();
vaultSubs = Subscription.EMPTY;
// trigger "default" change to refresh key bindings:
unlockButton.setDefaultButton(false);
unlockButton.setDefaultButton(true);
if (Objects.equals(this.vault, Objects.requireNonNull(vault))) {
return;
}
assert vault != null;
this.vault = vault;
advancedOptions.setVisible(false);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
unlockButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
downloadsPageLink.setManaged(false);
mountName.setText(vault.getMountName());
useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags());
mountFlags.setText(vault.getEffectiveMountFlags());
savePassword.setSelected(false);
// auto-fill pw from keychain:
if (keychainAccess.isPresent()) {
try {
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
if (storedPw != null) {
savePassword.setSelected(true);
passwordField.setPassword(storedPw);
passwordField.selectRange(storedPw.length, storedPw.length);
Arrays.fill(storedPw, ' ');
}
} catch (KeychainAccessException e) {
LOG.error("Failed to load stored password from system keychain.", e);
}
}
VaultSettings vaultSettings = vault.getVaultSettings();
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
useReadOnlyMode.setSelected(vaultSettings.usesReadOnlyMode().get());
// WEBDAV-dependent controls:
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
useCustomMountPoint.setVisible(false);
useCustomMountPoint.setManaged(false);
useCustomMountFlags.setVisible(false);
useCustomMountFlags.setManaged(false);
mountFlags.setVisible(false);
mountFlags.setManaged(false);
} else {
useCustomMountPoint.setVisible(true);
useCustomMountPoint.setSelected(vaultSettings.usesIndividualMountPath().get());
if (Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
customMountPointLabel.setText(localization.getString("unlock.label.chooseMountPath"));
} else {
customMountPointLabel.setText(displayablePath(vaultSettings.individualMountPath().getValueSafe()));
}
}
// OS-dependent controls:
if (SystemUtils.IS_OS_WINDOWS) {
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
winDriveLetter.getItems().clear();
winDriveLetter.getItems().add(null);
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
chooseSelectedDriveLetter();
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
useCustomWinDriveLetter.setSelected(!vaultSettings.usesIndividualMountPath().get());
useCustomWinDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
useCustomWinDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
}
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(useCustomMountPoint.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(useReadOnlyMode.selectedProperty(), vaultSettings.usesReadOnlyMode()::set));
}
private String displayablePath(String path) {
Path homeDir = Paths.get(SystemUtils.USER_HOME);
Path p = Paths.get(path);
if (p.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
return homePrefix + relativePath.toString();
} else {
return p.toString();
}
}
// ****************************************
// Downloads link
// ****************************************
@FXML
public void didClickDownloadsLink() {
app.getHostServices().showDocument("https://cryptomator.org/downloads/#allVersions");
}
// ****************************************
// Advanced options button
// ****************************************
@FXML
private void didClickAdvancedOptionsButton() {
messageText.setText(null);
downloadsPageLink.setManaged(false);
advancedOptions.setVisible(!advancedOptions.isVisible());
if (advancedOptions.isVisible()) {
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
} else {
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
}
}
private void filterAlphanumericKeyEvents(KeyEvent t) {
if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) {
t.consume();
}
}
private void mountNameDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused") String oldValue, String newValue) {
// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
if (newValue.isEmpty()) {
mountName.setText(vault.getMountName());
} else {
vault.setMountName(newValue);
}
if (!useCustomMountFlags.isSelected()) {
mountFlags.setText(vault.getEffectiveMountFlags()); // update default flags
}
}
private void useReadOnlyDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
vault.getVaultSettings().usesReadOnlyMode().setValue(newValue);
if (!useCustomMountFlags.isSelected()) {
mountFlags.setText(vault.getEffectiveMountFlags()); // update default flags
}
}
private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
if (!newValue) {
vault.setCustomMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS);
mountFlags.setText(vault.getEffectiveMountFlags());
}
}
private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused") String oldValue, String newValue) {
if (useCustomMountFlags.isSelected()) {
vault.setCustomMountFlags(newValue);
}
}
@FXML
public void didClickChooseCustomMountPoint() {
DirectoryChooser dirChooser = new DirectoryChooser();
File file = dirChooser.showDialog(mainWindow);
if (file != null) {
customMountPointLabel.setText(displayablePath(file.toString()));
vault.setCustomMountPath(file.toString());
}
}
@FXML
public void didClickCustomWinDriveLetterCheckbox() {
if (!useCustomWinDriveLetter.isSelected()) {
winDriveLetter.setValue(null);
}
}
@FXML
public void didClickCustomMountPointCheckbox() {
useCustomWinDriveLetter.setSelected(vault.getWinDriveLetter() != null);
}
/**
* Converts 'C' to "C:" to translate between model and GUI.
*/
private class WinDriveLetterLabelConverter extends StringConverter<Path> {
@Override
public String toString(Path root) {
if (root == null) {
return localization.getString("unlock.choicebox.winDriveLetter.auto");
} else if (root.endsWith("occupied")) {
return root.getRoot().toString().substring(0, 1) + " (" + localization.getString("unlock.choicebox.winDriveLetter.occupied") + ")";
} else {
return root.toString().substring(0, 1);
}
}
@Override
public Path fromString(String string) {
if (localization.getString("unlock.choicebox.winDriveLetter.auto").equals(string)) {
return null;
} else {
return Path.of(string);
}
}
}
/**
* Natural sorting of ASCII letters, but <code>null</code> always on first, as this is "auto-assign".
*/
private static class WinDriveLetterComparator implements Comparator<Path> {
@Override
public int compare(Path c1, Path c2) {
if (c1 == null) {
return -1;
} else if (c2 == null) {
return 1;
} else {
return c1.compareTo(c2);
}
}
}
private void winDriveLetterDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> property, @SuppressWarnings("unused") Path oldValue, Path newValue) {
vault.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 (vault.getWinDriveLetter() != null) {
final Path pickedRoot = Path.of(vault.getWinDriveLetter() + ":\\");
if (driveLetters.getOccupiedDriveLetters().contains(pickedRoot)) {
Path alteredPath = pickedRoot.resolve("occupied");
this.winDriveLetter.getItems().add(alteredPath);
this.winDriveLetter.getSelectionModel().select(alteredPath);
} else {
this.winDriveLetter.getSelectionModel().select(pickedRoot);
}
} else {
// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
this.winDriveLetter.getSelectionModel().selectFirst();
}
}
// ****************************************
// Save password checkbox
// ****************************************
@FXML
private void didClickSavePasswordCheckbox() {
if (!savePassword.isSelected() && hasStoredPassword()) {
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
localization.getString("unlock.savePassword.delete.confirmation.title"), //
localization.getString("unlock.savePassword.delete.confirmation.header"), //
localization.getString("unlock.savePassword.delete.confirmation.content"), //
SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);
Optional<ButtonType> choice = confirmDialog.showAndWait();
if (ButtonType.OK.equals(choice.get())) {
try {
keychainAccess.get().deletePassphrase(vault.getId());
} catch (KeychainAccessException e) {
LOG.error("Failed to remove entry from system keychain.", e);
}
} else if (ButtonType.CANCEL.equals(choice.get())) {
savePassword.setSelected(true);
}
}
}
private boolean hasStoredPassword() {
try {
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
boolean hasPw = (storedPw != null);
if (storedPw != null) {
Arrays.fill(storedPw, ' ');
}
return hasPw;
} catch (KeychainAccessException e) {
return false;
}
}
// ****************************************
// Unlock button
// ****************************************
@FXML
private void didClickUnlockButton() {
unlocking.set(true);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
unlockButton.setContentDisplay(ContentDisplay.LEFT);
CharSequence password = passwordField.getCharacters();
Tasks.create(() -> {
vault.unlock(password);
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
}
}).onSuccess(() -> {
messageText.setText(null);
downloadsPageLink.setManaged(false);
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
passwordField.swipe();
}).onError(InvalidPassphraseException.class, e -> {
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
downloadsPageLink.setManaged(false);
passwordField.selectAll();
passwordField.requestFocus();
}).onError(UnsupportedVaultFormatException.class, e -> {
if (e.isVaultOlderThanSoftware()) {
// whitespace after localized text used as separator between text and hyperlink
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
downloadsPageLink.setManaged(true);
} else if (e.isSoftwareOlderThanVault()) {
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
downloadsPageLink.setManaged(true);
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
}
}).onError(NotDirectoryException.class, e -> {
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
advancedOptions.setVisible(true);
messageText.setText(null);
downloadsPageLink.setManaged(false);
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNonExisting");
}).onError(DirectoryNotEmptyException.class, e -> {
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
advancedOptions.setVisible(true);
messageText.setText(null);
downloadsPageLink.setManaged(false);
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNotEmpty");
}).onError(Exception.class, e -> { // including RuntimeExceptions
LOG.error("Unlock failed for technical reasons.", e);
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
downloadsPageLink.setManaged(false);
}).andFinally(() -> {
unlocking.set(false);
unlockButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
}).runOnce(executor);
}
private void showUnlockFailedErrorDialog(String localizableContentKey) {
String title = localization.getString("unlock.failedDialog.title");
String header = localization.getString("unlock.failedDialog.header");
String content = localization.getString(localizableContentKey);
Alert alert = DialogBuilderUtil.buildErrorDialog(title, header, content, ButtonType.OK);
alert.show();
}
/* callback */
public void setListener(UnlockListener listener) {
this.listener = Optional.ofNullable(listener);
}
@FunctionalInterface
interface UnlockListener {
void didUnlock(Vault vault);
}
/* state */
public enum State {
UNLOCKING(null), //
INITIALIZED("unlock.successLabel.vaultCreated"), //
PASSWORD_CHANGED("unlock.successLabel.passwordChanged"), //
UPGRADED("unlock.successLabel.upgraded");
private Optional<String> successMessage;
State(String successMessage) {
this.successMessage = Optional.ofNullable(successMessage);
}
public Optional<String> successMessage() {
return successMessage;
}
}
}

View File

@@ -1,275 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.Parent;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.PopupWindow.AnchorLocation;
import javafx.util.Duration;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.common.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import static java.lang.String.format;
public class UnlockedController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(UnlockedController.class);
private static final int IO_SAMPLING_STEPS = 100;
private static final double IO_SAMPLING_INTERVAL = 0.5;
private final Localization localization;
private final ExecutorService executor;
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private Optional<LockListener> listener = Optional.empty();
private Timeline ioAnimation;
@FXML
private Label messageLabel;
@FXML
private LineChart<Number, Number> ioGraph;
@FXML
private NumberAxis xAxis;
@FXML
private ToggleButton moreOptionsButton;
@FXML
private ContextMenu moreOptionsMenu;
@FXML
private VBox root;
@Inject
public UnlockedController(Localization localization, ExecutorService executor) {
this.localization = localization;
this.executor = executor;
}
@Override
public void initialize() {
EasyBind.subscribe(vault, this::vaultChanged);
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
}
@Override
public Parent getRoot() {
return root;
}
private void vaultChanged(Vault newVault) {
if (newVault == null) {
return;
}
// (re)start throughput statistics:
stopIoSampling();
startIoSampling();
}
@FXML
private void didClickLockVault() {
regularLockVault(this::lockVaultSucceeded);
}
private void lockVaultSucceeded() {
listener.ifPresent(listener -> listener.didLock(this));
}
private void regularLockVault(Runnable onSuccess) {
Tasks.create(() -> {
vault.get().lock(false);
}).onSuccess(() -> {
LOG.trace("Regular unmount succeeded.");
onSuccess.run();
}).onError(Exception.class, e -> {
onRegularUnmountVaultFailed(e, onSuccess);
}).runOnce(executor);
}
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
if (vault.get().supportsForcedUnmount()) {
LOG.trace("Regular unmount failed.", e);
Alert confirmDialog = DialogBuilderUtil.buildYesNoDialog( //
format(localization.getString("unlocked.lock.force.confirmation.title"), vault.get().name().getValue()), //
localization.getString("unlocked.lock.force.confirmation.header"), //
localization.getString("unlocked.lock.force.confirmation.content"), //
ButtonType.NO);
Optional<ButtonType> choice = confirmDialog.showAndWait();
if (ButtonType.YES.equals(choice.get())) {
forcedLockVault(onSuccess);
} else {
LOG.trace("Unmount cancelled.", e);
}
} else {
LOG.error("Regular unmount failed.", e);
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}
}
private void forcedLockVault(Runnable onSuccess) {
Tasks.create(() -> {
vault.get().lock(true);
}).onSuccess(() -> {
LOG.trace("Forced unmount succeeded.");
onSuccess.run();
}).onError(Exception.class, e -> {
LOG.error("Forced unmount failed.", e);
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}).runOnce(executor);
}
@FXML
private void didClickMoreOptions() {
if (moreOptionsMenu.isShowing()) {
moreOptionsMenu.hide();
} else {
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
}
}
@FXML
private void didClickRevealVault() {
revealVault(vault.get());
}
void revealVault(Vault vault) {
Tasks.create(() -> {
vault.reveal();
}).onSuccess(() -> {
LOG.trace("Reveal succeeded.");
messageLabel.setText(null);
}).onError(Exception.class, e -> {
LOG.error("Reveal failed.", e);
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
}).runOnce(executor);
}
// ****************************************
// IO Graph
// ****************************************
private void startIoSampling() {
final Series<Number, Number> decryptedBytes = new Series<>();
decryptedBytes.setName(localization.getString("unlocked.label.statsDecrypted"));
final Series<Number, Number> encryptedBytes = new Series<>();
encryptedBytes.setName(localization.getString("unlocked.label.statsEncrypted"));
ioGraph.getData().add(decryptedBytes);
ioGraph.getData().add(encryptedBytes);
ioAnimation = new Timeline();
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(decryptedBytes, encryptedBytes)));
ioAnimation.setCycleCount(Animation.INDEFINITE);
ioAnimation.play();
}
private void stopIoSampling() {
if (ioAnimation != null) {
ioGraph.getData().clear();
ioAnimation.stop();
}
}
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
private static final double BYTES_TO_MEGABYTES_FACTOR = 1.0 / IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
private final Series<Number, Number> decryptedBytes;
private final Series<Number, Number> encryptedBytes;
public IoSamplingAnimationHandler(Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
this.decryptedBytes = decryptedBytes;
this.encryptedBytes = encryptedBytes;
// initialize data once and change value of datapoints later:
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
decryptedBytes.getData().add(new Data<>(i, 0));
encryptedBytes.getData().add(new Data<>(i, 0));
}
xAxis.setLowerBound(0);
xAxis.setUpperBound(IO_SAMPLING_STEPS);
}
@Override
public void handle(ActionEvent event) {
// move all values one step:
for (int i = 0; i < IO_SAMPLING_STEPS - 1; i++) {
int j = i + 1;
Number tmp = decryptedBytes.getData().get(j).getYValue();
decryptedBytes.getData().get(i).setYValue(tmp);
tmp = encryptedBytes.getData().get(j).getYValue();
encryptedBytes.getData().get(i).setYValue(tmp);
}
// add latest value:
final long decBytes = vault.get().pollBytesRead();
final double decMb = decBytes * BYTES_TO_MEGABYTES_FACTOR;
final long encBytes = vault.get().pollBytesWritten();
final double encMb = encBytes * BYTES_TO_MEGABYTES_FACTOR;
decryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(decMb);
encryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(encMb);
}
}
/* Getter/Setter */
public Vault getVault() {
return this.vault.get();
}
public void setVault(Vault vault) {
this.vault.set(vault);
}
/* callback */
public void setListener(LockListener listener) {
this.listener = Optional.ofNullable(listener);
}
@FunctionalInterface
interface LockListener {
void didLock(UnlockedController ctrl);
}
}

View File

@@ -1,162 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import org.cryptomator.ui.controls.SecurePasswordField;
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.Tasks;
import org.fxmisc.easybind.EasyBind;
public class UpgradeController implements ViewController {
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
private final UpgradeStrategies strategies;
private final ExecutorService executor;
private Optional<UpgradeListener> listener = Optional.empty();
private Vault vault;
@Inject
public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
this.strategies = strategies;
this.executor = executor;
}
@FXML
private Label upgradeTitleLabel;
@FXML
private Label upgradeMsgLabel;
@FXML
private SecurePasswordField passwordField;
@FXML
private CheckBox confirmationCheckbox;
@FXML
private Button upgradeButton;
@FXML
private ProgressIndicator progressIndicator;
@FXML
private Label errorLabel;
@FXML
private GridPane root;
@Override
public void initialize() {
upgradeTitleLabel.textProperty().bind(EasyBind.monadic(strategy).map(this::upgradeTitle).orElse(""));
upgradeMsgLabel.textProperty().bind(EasyBind.monadic(strategy).map(this::upgradeMessage).orElse(""));
BooleanExpression passwordProvided = passwordField.textProperty().isNotEmpty().and(passwordField.disabledProperty().not());
BooleanExpression syncFinished = confirmationCheckbox.selectedProperty();
upgradeButton.disableProperty().bind(passwordProvided.not().or(syncFinished.not()));
}
@Override
public Parent getRoot() {
return root;
}
@Override
public void focus() {
passwordField.requestFocus();
}
void setVault(Vault vault) {
this.vault = Objects.requireNonNull(vault);
errorLabel.setText(null);
strategy.set(strategies.getUpgradeStrategy(vault));
// trigger "default" change to refresh key bindings:
upgradeButton.setDefaultButton(false);
upgradeButton.setDefaultButton(true);
}
// ****************************************
// Upgrade label
// ****************************************
private String upgradeTitle(UpgradeStrategy instruction) {
return instruction.getTitle(vault);
}
private String upgradeMessage(UpgradeStrategy instruction) {
return instruction.getMessage(vault);
}
// ****************************************
// Upgrade button
// ****************************************
@FXML
private void didClickUpgradeButton(ActionEvent event) {
EasyBind.monadic(strategy).ifPresent(this::upgrade);
}
private void upgrade(UpgradeStrategy instruction) {
passwordField.setDisable(true);
progressIndicator.setVisible(true);
Tasks //
.create(() -> {
if (!instruction.isApplicable(vault)) {
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
}
instruction.upgrade(vault, passwordField.getCharacters());
}) //
.onSuccess(this::showNextUpgrade) //
.onError(UpgradeFailedException.class, e -> {
errorLabel.setText(e.getLocalizedMessage());
}) //
.andFinally(() -> {
progressIndicator.setVisible(false);
passwordField.setDisable(false);
passwordField.swipe();
}).runOnce(executor);
}
private void showNextUpgrade() {
errorLabel.setText(null);
UpgradeStrategy nextStrategy = strategies.getUpgradeStrategy(vault);
if (nextStrategy != null) {
strategy.set(nextStrategy);
} else {
listener.ifPresent(UpgradeListener::didUpgrade);
}
}
/* callback */
public void setListener(UpgradeListener listener) {
this.listener = Optional.ofNullable(listener);
}
@FunctionalInterface
interface UpgradeListener {
void didUpgrade();
}
}

View File

@@ -1,31 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
public interface ViewController extends Initializable {
Parent getRoot();
@Override
default void initialize(URL location, ResourceBundle resources) {
initialize();
}
default void initialize() {
// no-op
}
default void focus() {
// no-op
}
}

View File

@@ -1,23 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import dagger.MapKey;
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@MapKey
public @interface ViewControllerKey {
Class<? extends ViewController> value();
}

View File

@@ -1,51 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.fxml.FXMLLoader;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.ui.l10n.Localization;
import javax.inject.Inject;
import javax.inject.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Map;
@FxApplicationScoped
public class ViewControllerLoader {
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
private final Localization localization;
@Inject
public ViewControllerLoader(Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders, Localization localization) {
this.controllerProviders = controllerProviders;
this.localization = localization;
}
public <T extends ViewController> T load(String resourceName) {
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(this::constructController);
loader.setResources(localization);
try (InputStream in = getClass().getResourceAsStream(resourceName)) {
loader.load(in);
} catch (IOException e) {
throw new UncheckedIOException("Error loading FXML: " + resourceName, e);
}
return loader.getController();
}
private ViewController constructController(Class<?> clazz) {
Provider<ViewController> ctrlProvider = controllerProviders.get(clazz);
if (ctrlProvider == null) {
throw new IllegalStateException("No provider for type " + clazz.getName() + " registered.");
}
return ctrlProvider.get();
}
}

View File

@@ -1,78 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
@Module
public class ViewControllerModule {
@Provides
@IntoMap
@ViewControllerKey(ChangePasswordController.class)
ViewController provideChangePasswordController(ChangePasswordController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(InitializeController.class)
ViewController provideInitializeController(InitializeController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(MainController.class)
ViewController provideMainController(MainController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(NotFoundController.class)
ViewController provideNotFoundController(NotFoundController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(SettingsController.class)
ViewController provideSettingsController(SettingsController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(UnlockController.class)
ViewController provideUnlockController(UnlockController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(UnlockedController.class)
ViewController provideUnlockedController(UnlockedController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(UpgradeController.class)
ViewController provideUpgradeController(UpgradeController controller) {
return controller;
}
@Provides
@IntoMap
@ViewControllerKey(WelcomeController.class)
ViewController provideWelcomeController(WelcomeController controller) {
return controller;
}
}

View File

@@ -1,195 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014, 2017 Sebastian Stenzel
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controllers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.common.Tasks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
@FxApplicationScoped
public class WelcomeController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
private final Application app;
private final Optional<String> applicationVersion;
private final Localization localization;
private final Settings settings;
private final Comparator<String> semVerComparator;
private final ScheduledExecutorService executor;
@Inject
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
ScheduledExecutorService executor) {
this.app = app;
this.applicationVersion = applicationVersion;
this.localization = localization;
this.settings = settings;
this.semVerComparator = semVerComparator;
this.executor = executor;
}
@FXML
private Node checkForUpdatesContainer;
@FXML
private Label checkForUpdatesStatus;
@FXML
private ProgressIndicator checkForUpdatesIndicator;
@FXML
private Hyperlink updateLink;
@FXML
private VBox root;
@Override
public void initialize(URL location, ResourceBundle resources) {
if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false);
} else if (!settings.askedForUpdateCheck().get()) {
this.askForUpdateCheck();
} else if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
}
@Override
public Parent getRoot() {
return root;
}
// ****************************************
// Check for updates
// ****************************************
private boolean areUpdatesManagedExternally() {
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
}
private void askForUpdateCheck() {
Tasks.create(() -> {}).onSuccess(() -> {
Optional<ButtonType> result = buildYesNoDialog(
localization.getString("welcome.askForUpdateCheck.dialog.title"),
localization.getString("welcome.askForUpdateCheck.dialog.header"),
localization.getString("welcome.askForUpdateCheck.dialog.content"),
ButtonType.YES).showAndWait();
if (result.isPresent()) {
settings.askedForUpdateCheck().set(true);
settings.checkForUpdates().set(result.get().equals(ButtonType.YES));
}
if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
}).scheduleOnce(executor, 1, TimeUnit.SECONDS);
}
private void checkForUpdates() {
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
checkForUpdatesIndicator.setVisible(true);
Tasks.create(() -> {
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("User-Agent", userAgent);
conn.connect();
try {
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
return Optional.<byte[]>empty();
}
try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
return Optional.of(out.toByteArray());
}
} finally {
conn.disconnect();
}
}).onSuccess(response -> {
response.ifPresent(bytes -> {
Gson gson = new GsonBuilder().setLenient().create();
String json = new String(bytes, StandardCharsets.UTF_8);
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
}.getType());
if (map != null) {
this.compareVersions(map);
}
});
}).onError(Exception.class, e -> {
LOG.warn("Error checking for updates", e);
}).andFinally(() -> {
checkForUpdatesStatus.setText("");
checkForUpdatesIndicator.setVisible(false);
}).runOnce(executor);
}
private void compareVersions(final Map<String, String> latestVersions) {
assert Platform.isFxApplicationThread();
final String latestVersion;
if (SystemUtils.IS_OS_MAC_OSX) {
latestVersion = latestVersions.get("mac");
} else if (SystemUtils.IS_OS_WINDOWS) {
latestVersion = latestVersions.get("win");
} else if (SystemUtils.IS_OS_LINUX) {
latestVersion = latestVersions.get("linux");
} else {
// no version check possible on unsupported OS
return;
}
final String currentVersion = applicationVersion.orElse(null);
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
this.updateLink.setText(msg);
this.updateLink.setVisible(true);
this.updateLink.setDisable(false);
}
}
@FXML
public void didClickUpdateLink(ActionEvent event) {
app.getHostServices().showDocument("https://cryptomator.org/");
}
}

View File

@@ -1,107 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.controls;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.fxmisc.easybind.EasyBind;
import javafx.beans.binding.ObjectExpression;
import javafx.geometry.Pos;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
@Deprecated(forRemoval = true, since = "1.5.0")
public class DirectoryListCell extends DraggableListCell<Vault> {
private static final Color UNLOCKED_ICON_COLOR = new Color(0.901, 0.494, 0.133, 1.0);
private final Label statusText = new Label();
private final Label nameText = new Label();
private final Label pathText = new Label();
private final VBox vbox = new VBox(4.0, nameText, pathText);
private final HBox hbox = new HBox(6.0, statusText, vbox);
private ContextMenu vaultContextMenu;
public DirectoryListCell() {
ObjectExpression<VaultState> vaultState = ObjectExpression.objectExpression(EasyBind.select(itemProperty()).selectObject(Vault::stateProperty));
hbox.setAlignment(Pos.CENTER_LEFT);
hbox.setPrefWidth(1);
vbox.setFillWidth(true);
nameText.textProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::name));
nameText.textFillProperty().bind(this.textFillProperty());
nameText.fontProperty().bind(this.fontProperty());
pathText.textProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath));
pathText.setTextOverrun(OverrunStyle.ELLIPSIS);
pathText.getStyleClass().add("detail-label");
statusText.textProperty().bind(EasyBind.map(vaultState, this::getStatusIconText));
statusText.textFillProperty().bind(EasyBind.combine(vaultState, textFillProperty(), this::getStatusIconColor));
statusText.setMinSize(16.0, 16.0);
statusText.setAlignment(Pos.CENTER);
statusText.getStyleClass().add("fontawesome");
tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath).map(p -> new Tooltip(p.toString())));
contextMenuProperty().bind(EasyBind.map(vaultState, this::getContextMenu));
setGraphic(hbox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
private String getStatusIconText(VaultState state) {
if (state == null) {
return "";
}
switch (state) {
case UNLOCKED:
case PROCESSING:
return "\uf09c";
case LOCKED:
default:
return "\uf023";
}
}
private Paint getStatusIconColor(VaultState state, Paint lockedValue) {
if (state == null) {
return lockedValue;
}
switch (state) {
case UNLOCKED:
case PROCESSING:
return UNLOCKED_ICON_COLOR;
case LOCKED:
default:
return lockedValue;
}
}
private ContextMenu getContextMenu(VaultState state) {
if (state == VaultState.LOCKED) {
return vaultContextMenu;
} else {
return null;
}
}
public void setVaultContextMenu(ContextMenu contextMenu) {
this.vaultContextMenu = contextMenu;
}
}

View File

@@ -1,100 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
import javafx.collections.ObservableList;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.Volume;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
@Deprecated(forRemoval = true, since = "1.5.0")
@FxApplicationScoped
public class AutoUnlocker {
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
private static final int NAP_TIME_MILLIS = 500;
private final Optional<KeychainAccess> keychainAccess;
private final ObservableList<Vault> vaults;
private final ExecutorService executor;
@Inject
public AutoUnlocker(Optional<KeychainAccess> keychainAccess, ObservableList<Vault> vaults, ExecutorService executor) {
this.keychainAccess = keychainAccess;
this.vaults = vaults;
this.executor = executor;
}
public void unlockAllSilently() {
Collection<Vault> vaultsToUnlock = vaults.stream().filter(this::shouldUnlockAfterStartup).collect(Collectors.toList());
if (keychainAccess.isPresent() && !vaultsToUnlock.isEmpty()) {
executor.submit(() -> unlockAll(vaultsToUnlock));
}
}
private boolean shouldUnlockAfterStartup(Vault vault) {
return vault.getVaultSettings().unlockAfterStartup().get();
}
private void unlockAll(Collection<Vault> vaults) {
try {
Iterator<Vault> iterator = vaults.iterator();
assert iterator.hasNext() : "vaults must not be empty";
unlockSilently(iterator.next());
while (iterator.hasNext()) {
Thread.sleep(NAP_TIME_MILLIS);
unlockSilently(iterator.next());
}
} catch (InterruptedException e) {
LOG.warn("Auto unlock thread interrupted.");
Thread.currentThread().interrupt();
}
}
private void unlockSilently(Vault vault) {
char[] storedPw = new char[0];
try {
storedPw = keychainAccess.get().loadPassphrase(vault.getId());
if (storedPw == null) {
LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath());
return;
}
vault.unlock(CharBuffer.wrap(storedPw));
revealSilently(vault);
} catch (IOException | CryptoException | Volume.VolumeException | KeychainAccessException e) {
LOG.error("Auto unlock failed.", e);
} finally {
Arrays.fill(storedPw, ' ');
}
}
private void revealSilently(Vault mountedVault) {
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
return;
}
try {
mountedVault.reveal();
} catch (Volume.VolumeException e) {
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
}
}
}

View File

@@ -1,34 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.common.vaults.Vault;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
@FxApplicationScoped
public class UpgradeStrategies {
private final Collection<UpgradeStrategy> strategies;
@Inject
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2, UpgradeVersion4to5 upgrader3, UpgradeVersion5toX upgrader4) {
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2, upgrader3, upgrader4));
}
public UpgradeStrategy getUpgradeStrategy(Vault vault) {
Objects.requireNonNull(vault);
return strategies.stream().filter(strategy -> {
return strategy.isApplicable(vault);
}).findFirst().orElse(null);
}
}

View File

@@ -1,138 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.KeyFile;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
protected final CryptorProvider cryptorProvider;
protected final Localization localization;
protected final int vaultVersionBeforeUpgrade;
protected final int vaultVersionAfterUpgrade;
UpgradeStrategy(CryptorProvider cryptorProvider, Localization localization, int vaultVersionBeforeUpgrade, int vaultVersionAfterUpgrade) {
this.cryptorProvider = cryptorProvider;
this.localization = localization;
this.vaultVersionBeforeUpgrade = vaultVersionBeforeUpgrade;
this.vaultVersionAfterUpgrade = vaultVersionAfterUpgrade;
}
static SecureRandom strongSecureRandom() {
try {
return SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e);
}
}
/**
* @return Localized title string to display to the user when an upgrade is needed.
*/
public abstract String getTitle(Vault vault);
/**
* @return Localized message string to display to the user when an upgrade is needed.
*/
public abstract String getMessage(Vault vault);
/**
* Upgrades a vault. Might take a moment, should be run in a background thread.
*/
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
LOG.info("Upgrading {} from {} to {}.", vault.getPath(), vaultVersionBeforeUpgrade, vaultVersionAfterUpgrade);
final Path masterkeyFileBeforeUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME);
try (Cryptor cryptor = readMasterkeyFile(masterkeyFileBeforeUpgrade, passphrase)) {
// create backup, as soon as we know the password was correct:
Path masterkeyBackupFile = vault.getPath().resolve(MASTERKEY_BACKUP_FILENAME);
Files.copy(masterkeyFileBeforeUpgrade, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Backuped masterkey.");
// do stuff:
upgrade(vault, cryptor);
// write updated masterkey file:
Path masterkeyFileAfterUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME); // path may have changed
writeMasterkeyFile(masterkeyFileAfterUpgrade, cryptor, passphrase);
LOG.info("Updated masterkey.");
} catch (InvalidPassphraseException e) {
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
} catch (UnsupportedVaultFormatException e) {
if (e.getDetectedVersion() == Integer.MAX_VALUE) {
LOG.warn("Version MAC authentication error in vault {}", vault.getPath());
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
} else {
LOG.warn("Upgrade failed.", e);
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
}
} catch (IOException e) {
LOG.warn("Upgrade failed.", e);
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
}
}
protected Cryptor readMasterkeyFile(Path masterkeyFile, CharSequence passphrase) throws UnsupportedVaultFormatException, InvalidPassphraseException, IOException {
byte[] fileContents = Files.readAllBytes(masterkeyFile);
KeyFile keyFile = KeyFile.parse(fileContents);
return cryptorProvider.createFromKeyFile(keyFile, passphrase, vaultVersionBeforeUpgrade);
}
protected void writeMasterkeyFile(Path masterkeyFile, Cryptor cryptor, CharSequence passphrase) throws IOException {
byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize();
Files.write(masterkeyFile, fileContents, StandardOpenOption.TRUNCATE_EXISTING);
}
protected abstract void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException;
/**
* Determines in O(1), if an upgrade can be applied to a vault.
*
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
*/
public boolean isApplicable(Vault vault) {
final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME);
try {
byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
return KeyFile.parse(masterkeyFileContents).getVersion() == vaultVersionBeforeUpgrade;
} catch (IOException e) {
LOG.warn("Could not determine, whether upgrade is applicable.", e);
return false;
}
}
/**
* Thrown when data migration failed.
*/
public static class UpgradeFailedException extends Exception {
UpgradeFailedException() {
}
UpgradeFailedException(String message) {
super(message);
}
}
}

View File

@@ -1,81 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import javafx.application.Platform;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@FxApplicationScoped
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
@Inject
public UpgradeVersion3DropBundleExtension(Localization localization) {
super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 3, 3);
}
@Override
public String getTitle(Vault vault) {
return localization.getString("upgrade.version3dropBundleExtension.title");
}
@Override
public String getMessage(Vault vault) {
String fmt = localization.getString("upgrade.version3dropBundleExtension.msg");
Path path = vault.getPath();
String oldVaultName = path.getFileName().toString();
String newVaultName = StringUtils.removeEnd(oldVaultName, ".cryptomator");
return String.format(fmt, oldVaultName, newVaultName);
}
@Override
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
Path path = vault.getPath();
String oldVaultName = path.getFileName().toString();
String newVaultName = StringUtils.removeEnd(oldVaultName, ".cryptomator");
Path newPath = path.resolveSibling(newVaultName);
if (Files.exists(newPath)) {
String fmt = localization.getString("upgrade.version3dropBundleExtension.err.alreadyExists");
String msg = String.format(fmt, newPath);
throw new UpgradeFailedException(msg);
} else {
try {
LOG.info("Renaming {} to {}", path, newPath.getFileName());
Files.move(path, path.resolveSibling(newVaultName));
Platform.runLater(() -> {
vault.getVaultSettings().path().set(newPath);
});
} catch (IOException e) {
LOG.error("Vault migration failed", e);
throw new UpgradeFailedException();
}
}
}
@Override
public boolean isApplicable(Vault vault) {
Path vaultPath = vault.getPath();
if (vaultPath.toString().endsWith(".cryptomator")) {
return super.isApplicable(vault);
} else {
return false;
}
}
}

View File

@@ -1,171 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
*/
@FxApplicationScoped
class UpgradeVersion3to4 extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
private static final Pattern LVL1_DIR_PATTERN = Pattern.compile("[A-Z2-7]{2}");
private static final Pattern LVL2_DIR_PATTERN = Pattern.compile("[A-Z2-7]{30}");
private static final Pattern BASE32_PATTERN = Pattern.compile("^(([A-Z2-7]{8})*[A-Z2-7=]{8})");
private static final Pattern BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN = Pattern.compile(BASE32_PATTERN.pattern() + "_");
private static final int FILE_MIN_SIZE = 88; // vault version 3 files have a header of 88 bytes (assuming no chunks at all)
private static final String LONG_FILENAME_EXT = ".lng";
private static final String OLD_FOLDER_SUFFIX = "_";
private static final String NEW_FOLDER_PREFIX = "0";
private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
private final BaseEncoding base32 = BaseEncoding.base32();
@Inject
public UpgradeVersion3to4(Localization localization) {
super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 3, 4);
}
@Override
public String getTitle(Vault vault) {
return localization.getString("upgrade.version3to4.title");
}
@Override
public String getMessage(Vault vault) {
return localization.getString("upgrade.version3to4.msg");
}
@Override
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
Path dataDir = vault.getPath().resolve("d");
Path metadataDir = vault.getPath().resolve("m");
if (!Files.isDirectory(dataDir)) {
return; // empty vault. no migration needed.
}
try {
Files.walkFileTree(dataDir, EnumSet.noneOf(FileVisitOption.class), 3, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.equals(dataDir)) {
// path/to/vault/d
return FileVisitResult.CONTINUE;
} else if (dir.getParent().equals(dataDir) && LVL1_DIR_PATTERN.matcher(dir.getFileName().toString()).matches()) {
// path/to/vault/d/AB
return FileVisitResult.CONTINUE;
} else if (dir.getParent().getParent().equals(dataDir) && LVL2_DIR_PATTERN.matcher(dir.getFileName().toString()).matches()) {
// path/to/vault/d/AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567
return FileVisitResult.CONTINUE;
} else {
LOG.info("Skipping irrelevant directory {}", dir);
return FileVisitResult.SKIP_SUBTREE;
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String name = file.getFileName().toString();
if (attrs.size() > FILE_MIN_SIZE) {
LOG.trace("Skipping non-directory file {}.", file);
} else if (name.endsWith(LONG_FILENAME_EXT)) {
migrateLong(metadataDir, file);
} else {
migrate(file);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
LOG.error("Migration failed.", e);
throw new UpgradeFailedException(localization.getString("upgrade.version3to4.err.io"));
}
LOG.info("Migration finished.");
}
private void migrate(Path file) throws IOException {
String name = file.getFileName().toString();
Matcher m = BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN.matcher(name);
if (m.find(0)) {
String base32 = m.group(1);
String suffix = name.substring(m.end());
String renamed = NEW_FOLDER_PREFIX + base32 + suffix;
renameWithoutOverwriting(file, renamed);
}
}
private void renameWithoutOverwriting(Path path, String newName) throws IOException {
Path newPath = path.resolveSibling(newName);
for (int i = 2; Files.exists(newPath); i++) {
newPath = path.resolveSibling(newName + " " + i);
}
Files.move(path, newPath);
LOG.info("Renaming {} to {}", path, newPath.getFileName());
}
private void migrateLong(Path metadataDir, Path path) throws IOException {
String oldName = path.getFileName().toString();
assert oldName.endsWith(LONG_FILENAME_EXT);
String oldNameBase = StringUtils.removeEnd(oldName, LONG_FILENAME_EXT);
Matcher m = BASE32_PATTERN.matcher(oldNameBase);
if (m.find(0)) {
String oldNameBase32 = m.group(1);
String oldNameSuffix = oldNameBase.substring(m.end());
String oldCanonicalName = oldNameBase32 + LONG_FILENAME_EXT;
Path oldMetadataFile = metadataDir.resolve(oldCanonicalName.substring(0, 2)).resolve(oldCanonicalName.substring(2, 4)).resolve(oldCanonicalName);
if (!Files.isReadable(oldMetadataFile)) {
LOG.warn("Found uninflatable long file name. Expected: {}", oldMetadataFile);
return;
}
String oldLongName = new String(Files.readAllBytes(oldMetadataFile), UTF_8);
if (oldLongName.endsWith(OLD_FOLDER_SUFFIX)) {
String newLongName = NEW_FOLDER_PREFIX + StringUtils.removeEnd(oldLongName, OLD_FOLDER_SUFFIX);
String newCanonicalBase32 = base32.encode(sha1.digest(newLongName.getBytes(UTF_8)));
String newCanonicalName = newCanonicalBase32 + LONG_FILENAME_EXT;
Path newMetadataFile = metadataDir.resolve(newCanonicalName.substring(0, 2)).resolve(newCanonicalName.substring(2, 4)).resolve(newCanonicalName);
String newName = newCanonicalBase32 + oldNameSuffix + LONG_FILENAME_EXT;
Path newPath = path.resolveSibling(newName);
Files.move(path, newPath);
Files.createDirectories(newMetadataFile.getParent());
Files.write(newMetadataFile, newLongName.getBytes(UTF_8));
LOG.info("Renaming {} to {}.", path, newName);
LOG.info("Creating {}.", newMetadataFile);
}
}
}
}

View File

@@ -1,167 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
import java.util.regex.Pattern;
/**
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
*/
@FxApplicationScoped
class UpgradeVersion4to5 extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
private static final Pattern LVL1_DIR_PATTERN = Pattern.compile("[A-Z2-7]{2}");
private static final Pattern LVL2_DIR_PATTERN = Pattern.compile("[A-Z2-7]{30}");
private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}");
@Inject
public UpgradeVersion4to5(Localization localization) {
super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 4, 5);
}
@Override
public String getTitle(Vault vault) {
return localization.getString("upgrade.version4to5.title");
}
@Override
public String getMessage(Vault vault) {
return localization.getString("upgrade.version4to5.msg");
}
@Override
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
Path dataDir = vault.getPath().resolve("d");
if (!Files.isDirectory(dataDir)) {
return; // empty vault. no migration needed.
}
try {
Files.walkFileTree(dataDir, EnumSet.noneOf(FileVisitOption.class), 3, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (dir.equals(dataDir)) {
// path/to/vault/d
return FileVisitResult.CONTINUE;
} else if (dir.getParent().equals(dataDir) && LVL1_DIR_PATTERN.matcher(dir.getFileName().toString()).matches()) {
// path/to/vault/d/AB
return FileVisitResult.CONTINUE;
} else if (dir.getParent().getParent().equals(dataDir) && LVL2_DIR_PATTERN.matcher(dir.getFileName().toString()).matches()) {
// path/to/vault/d/AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567
return FileVisitResult.CONTINUE;
} else {
LOG.info("Skipping irrelevant directory {}", dir);
return FileVisitResult.SKIP_SUBTREE;
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (BASE32_PATTERN.matcher(file.getFileName().toString()).find() && attrs.size() > cryptor.fileHeaderCryptor().headerSize()) {
migrate(file, attrs, cryptor);
} else {
LOG.info("Skipping irrelevant file {}.", file);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
LOG.error("Migration failed.", e);
throw new UpgradeFailedException(localization.getString("upgrade.version4to5.err.io"));
}
LOG.info("Migration finished.");
}
@SuppressWarnings("deprecation")
private void migrate(Path file, BasicFileAttributes attrs, Cryptor cryptor) throws IOException {
LOG.info("Starting migration of {}...", file);
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// read header:
ByteBuffer headerBuf = ByteBuffer.allocate(cryptor.fileHeaderCryptor().headerSize());
ch.read(headerBuf);
headerBuf.flip();
LOG.info("\tHeader read");
FileHeader header = cryptor.fileHeaderCryptor().decryptHeader(headerBuf);
long cleartextSize = header.getFilesize();
if (cleartextSize < 0) {
LOG.info("\tSkipping already migrated file");
return;
} else if (cleartextSize > attrs.size()) {
LOG.warn("\tSkipping file with invalid file size {}/{}", cleartextSize, attrs.size());
return;
}
int headerSize = cryptor.fileHeaderCryptor().headerSize();
int ciphertextChunkSize = cryptor.fileContentCryptor().ciphertextChunkSize();
int cleartextChunkSize = cryptor.fileContentCryptor().cleartextChunkSize();
long newCiphertextSize = Cryptors.ciphertextSize(cleartextSize, cryptor);
long newEOF = headerSize + newCiphertextSize;
long newFullChunks = newCiphertextSize / ciphertextChunkSize; // int-truncation
long newAdditionalCiphertextBytes = newCiphertextSize % ciphertextChunkSize;
if (newAdditionalCiphertextBytes == 0) {
// (new) last block is already correct. just truncate:
LOG.info("\tMigrating cleartext size {}: Truncating to new ciphertext size: {}", cleartextSize, newEOF);
ch.truncate(newEOF);
LOG.info("\tFile truncated");
} else {
// last block may contain padding and needs to be re-encrypted:
long lastChunkIdx = newFullChunks;
LOG.info("\tMigrating cleartext size {}: Re-encrypting chunk {}. New ciphertext size: {}", cleartextSize, lastChunkIdx, newEOF);
long beginOfLastChunk = headerSize + lastChunkIdx * ciphertextChunkSize;
assert beginOfLastChunk < newEOF;
int lastCleartextChunkLength = (int) (cleartextSize % cleartextChunkSize);
assert lastCleartextChunkLength < cleartextChunkSize;
assert lastCleartextChunkLength > 0;
ch.position(beginOfLastChunk);
ByteBuffer lastCiphertextChunk = ByteBuffer.allocate(ciphertextChunkSize);
int read = ch.read(lastCiphertextChunk);
if (read != -1) {
lastCiphertextChunk.flip();
ByteBuffer lastCleartextChunk = cryptor.fileContentCryptor().decryptChunk(lastCiphertextChunk, lastChunkIdx, header, true);
lastCleartextChunk.position(0).limit(lastCleartextChunkLength);
assert lastCleartextChunk.remaining() == lastCleartextChunkLength;
ByteBuffer newLastChunkCiphertext = cryptor.fileContentCryptor().encryptChunk(lastCleartextChunk, lastChunkIdx, header);
ch.truncate(beginOfLastChunk);
ch.write(newLastChunkCiphertext);
} else {
LOG.error("\tReached EOF at position {}/{}", beginOfLastChunk, newEOF);
return; // must exit method before changing header!
}
LOG.info("\tReencrypted last block");
}
header.setFilesize(-1l);
ByteBuffer newHeaderBuf = cryptor.fileHeaderCryptor().encryptHeader(header);
ch.position(0);
ch.write(newHeaderBuf);
LOG.info("\tUpdated header");
}
LOG.info("Finished migration of {}.", file);
}
}

View File

@@ -1,70 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.common.vaults.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
@FxApplicationScoped
class UpgradeVersion5toX extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
@Inject
public UpgradeVersion5toX(Localization localization) {
super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 5, Integer.MAX_VALUE);
}
@Override
public String getTitle(Vault vault) {
return localization.getString("upgrade.version5toX.title");
}
@Override
public String getMessage(Vault vault) {
return localization.getString("upgrade.version5toX.msg");
}
@Override
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
try {
Migrators.get().migrate(vault.getPath(), "masterkey.cryptomator", passphrase);
} catch (InvalidPassphraseException e) {
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
} catch (NoApplicableMigratorException | IOException e) {
LOG.warn("Upgrade failed.", e);
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
}
}
@Override
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
// called by #upgrade(Vault, CharSequence), which got overwritten.
throw new AssertionError("Method can not be called.");
}
@Override
public boolean isApplicable(Vault vault) {
try {
return Migrators.get().needsMigration(vault.getPath(), "masterkey.cryptomator");
} catch (IOException e) {
LOG.warn("Could not determine, whether upgrade is applicable.", e);
return false;
}
}
}

View File

@@ -65,6 +65,7 @@ public class QuitController implements FxController {
lockAndQuitButton.setDisable(false);
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
response.cancelQuit();
});
lockAllService.start();

View File

@@ -36,7 +36,6 @@ import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.NotDirectoryException;
import java.util.Arrays;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
@UnlockScoped
@@ -49,7 +48,6 @@ public class UnlockController implements FxController {
private final ExecutorService executor;
private final ObjectBinding<ContentDisplay> unlockButtonState;
private final Optional<KeychainAccess> keychainAccess;
private final ResourceBundle resourceBundle;
private final Lazy<Scene> successScene;
private final ForgetPasswordComponent.Builder forgetPassword;
private final BooleanProperty unlockButtonDisabled;
@@ -57,13 +55,12 @@ public class UnlockController implements FxController {
public CheckBox savePassword;
@Inject
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, ResourceBundle resourceBundle, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, ForgetPasswordComponent.Builder forgetPassword) {
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, ForgetPasswordComponent.Builder forgetPassword) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
this.keychainAccess = keychainAccess;
this.resourceBundle = resourceBundle;
this.successScene = successScene;
this.forgetPassword = forgetPassword;
this.unlockButtonDisabled = new SimpleBooleanProperty();
@@ -126,7 +123,7 @@ public class UnlockController implements FxController {
@FXML
private void didClickSavePasswordCheckbox() {
if (!savePassword.isSelected() && hasStoredPassword()) {
forgetPassword.vault(vault).build().showForgetPassword().thenAccept(forgotten -> savePassword.setSelected(!forgotten));
forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePassword.setSelected(!forgotten));
}
}

View File

@@ -1,61 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
*
* Contributors:
* Jean-Noël Charon - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.util;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.text.Text;
public class DialogBuilderUtil {
public DialogBuilderUtil() {
}
public static Alert buildInformationDialog(String title, String header, String content, ButtonType defaultButton) {
return buildDialog(title, header, content, Alert.AlertType.INFORMATION, defaultButton);
}
public static Alert buildWarningDialog(String title, String header, String content, ButtonType defaultButton) {
return buildDialog(title, header, content, Alert.AlertType.WARNING, defaultButton);
}
public static Alert buildErrorDialog(String title, String header, String content, ButtonType defaultButton) {
return buildDialog(title, header, content, Alert.AlertType.ERROR, defaultButton);
}
public static Alert buildConfirmationDialog(String title, String header, String content, ButtonType defaultButton) {
return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton);
}
public static Alert buildYesNoDialog(String title, String header, String content, ButtonType defaultButton) {
return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton, ButtonType.YES, ButtonType.NO);
}
public static Alert buildGracefulShutdownDialog(String title, String header, String content, ButtonType defaultButton, ButtonType... buttons) {
return buildDialog(title, header, content, Alert.AlertType.WARNING, defaultButton, buttons);
}
private static Alert buildDialog(String title, String header, String content, Alert.AlertType type, ButtonType defaultButton, ButtonType... buttons) {
Text contentText = new Text(content);
contentText.setWrappingWidth(360.0);
Alert alert = new Alert(type, content, buttons);
alert.setTitle(title);
alert.setHeaderText(header);
alert.getDialogPane().setContent(contentText);
alert.getDialogPane().getButtonTypes().stream().forEach(buttonType -> {
Button btn = (Button) alert.getDialogPane().lookupButton(buttonType);
btn.setDefaultButton(buttonType.equals(defaultButton));
});
return alert;
}
}

View File

@@ -1,576 +0,0 @@
/*
* Copyright (c) 2015 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
* Jean-Noël Charon - implementation of the dialog css
*
*/
.root {
-fx-font-family: 'Cantarell';
-fx-font-smoothing-type: lcd;
-fx-font-size: 12px;
COLOR_TEXT: #444;
COLOR_TEXT_LIGHT: #888;
COLOR_TEXT_DISABLED: #BBB;
COLOR_HYPERLINK: #0069D9;
COLOR_BORDER: #D3D3D3;
COLOR_BORDER_DARK: #AAA;
COLOR_BACKGROUND: #F0F0F0;
COLOR_VGRAD_LIGHT: linear-gradient(to bottom, #FEFEFE 0%, #F5F5F5 100%);
COLOR_VGRAD_MEDIUM: linear-gradient(to bottom, #F5F5F5 0%, #F1F1F1 100%);
COLOR_VGRAD_DARK: linear-gradient(to bottom, #F1F1F1 0%, #E9E9E9 100%);
COLOR_HGRAD_LIGHT: linear-gradient(to right, #FEFEFE 0%, #F5F5F5 100%);
COLOR_HGRAD_MEDIUM: linear-gradient(to right, #F5F5F5 0%, #FEFEFE 100%);
COLOR_HGRAD_DARK: linear-gradient(to right, #F1F1F1 0%, #E9E9E9 100%);
COLOR_CHART_GREEN: #28CA40;
COLOR_CHART_RED: #FD4943;
-fx-background-color: COLOR_BACKGROUND;
-fx-text-fill: COLOR_TEXT;
}
/****************************************************************************
* *
* Labels & Fonts *
* *
****************************************************************************/
.label {
-fx-text-fill: COLOR_TEXT;
}
.ionicons {
-fx-font-family: Ionicons;
}
.fontawesome {
-fx-font-family: FontAwesome;
}
.caption-label {
-fx-font-size: 0.9em;
}
/****************************************************************************
* *
* Hyperlinks *
* *
****************************************************************************/
.hyperlink {
-fx-cursor: hand;
-fx-text-fill: COLOR_HYPERLINK;
}
.hyperlink:hover {
-fx-underline: true;
}
/****************************************************************************
* *
* Buttons *
* *
****************************************************************************/
.button,
.toggle-button {
-fx-pref-height: 25px;
-fx-background-color: COLOR_BORDER, COLOR_VGRAD_LIGHT;
-fx-background-insets: 0, 1;
-fx-padding: 4px 12px 6px 12px;
-fx-text-fill: COLOR_TEXT;
-fx-alignment: CENTER;
}
.button:hover,
.button:default:hover,
.toggle-button:hover {
-fx-background-color: COLOR_BORDER, COLOR_VGRAD_MEDIUM;
}
.button:armed,
.button:default:armed,
.toggle-button:armed {
-fx-background-color: COLOR_BORDER_DARK, COLOR_VGRAD_DARK;
}
.button:disabled,
.button:default:disabled,
.toggle-button:disabled {
-fx-background-color: COLOR_BORDER, COLOR_VGRAD_LIGHT;
-fx-text-fill: COLOR_TEXT_DISABLED;
}
.button:focused,
.toggle-button:focused {
-fx-border-color: black;
-fx-border-insets: 2px;
-fx-border-style: dotted inside;
}
/****************************************************************************
* *
* Segmented Buttons *
* *
****************************************************************************/
.segmented-button-bar .button,
.segmented-button-bar .toggle-button {
-fx-background-insets: 0, 1;
}
.segmented-button-bar .button.last,
.segmented-button-bar .toggle-button.last {
-fx-background-insets: 0, 1 1 1 0;
}
/****************************************************************************
* *
* Text Fields *
* *
****************************************************************************/
.text-input {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0.4em;
-fx-cursor: text;
}
.text-input:focused {
-fx-background-color: COLOR_BORDER_DARK, #FFF;
}
.text-input:disabled {
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
-fx-text-fill: COLOR_TEXT_DISABLED;
}
/****************************************************************************
* *
* Vault List *
* *
****************************************************************************/
.list-view {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
-fx-padding: 1;
}
.list-view:focused {
-fx-border-style: dotted inside;
}
.list-cell {
-fx-padding: 0.5em;
-fx-text-fill: COLOR_TEXT;
}
.list-cell:selected {
-fx-background-color: COLOR_VGRAD_DARK;
}
.list-cell .detail-label {
-fx-text-fill: COLOR_TEXT_LIGHT;
-fx-font-size: 0.8em;
}
/*******************************************************************************
* *
* ScrollBar *
* *
******************************************************************************/
.scroll-bar:vertical {
-fx-background-color: COLOR_HGRAD_MEDIUM;
-fx-border-color: transparent transparent transparent COLOR_BORDER;
-fx-padding: 2px;
}
.scroll-bar > .thumb {
-fx-background-color: COLOR_BORDER, COLOR_HGRAD_LIGHT;
-fx-background-insets: 0, 1;
-fx-background-radius: 5;
}
.scroll-bar > .thumb:hover,
.scroll-bar > .thumb:pressed {
-fx-background-color: COLOR_BORDER, COLOR_HGRAD_DARK;
}
.scroll-bar:vertical > .increment-button,
.scroll-bar:vertical > .decrement-button {
-fx-padding: 0 5 0 5;
}
/****************************************************************************
* *
* Vault List Toolbar *
* *
****************************************************************************/
.tool-bar.list-related-toolbar {
-fx-background-color: COLOR_BORDER, COLOR_VGRAD_LIGHT;
-fx-background-insets: 0, 0 1 1 1;
-fx-padding: 0;
-fx-spacing: 0;
-fx-alignment: CENTER_LEFT;
}
.tool-bar.list-related-toolbar .spacer {
-fx-border-color: transparent COLOR_BORDER transparent transparent;
-fx-border-width: 1;
}
.tool-bar.list-related-toolbar .toggle-button,
.tool-bar.list-related-toolbar .button {
-fx-pref-height: 30px;
-fx-font-size: 1.8em;
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0.2em 0.8em 0.2em 0.8em;
-fx-background-color: transparent;
-fx-background-insets: 1;
-fx-border-color: transparent COLOR_BORDER transparent transparent;
-fx-border-width: 1;
}
.tool-bar.list-related-toolbar .toggle-button:disabled,
.tool-bar.list-related-toolbar .button:disabled {
-fx-text-fill: COLOR_TEXT_DISABLED;
}
.tool-bar.list-related-toolbar .toggle-button:hover,
.tool-bar.list-related-toolbar .button:hover {
-fx-background-color: COLOR_VGRAD_MEDIUM;
}
.tool-bar.list-related-toolbar .toggle-button:armed,
.tool-bar.list-related-toolbar .toggle-button:selected,
.tool-bar.list-related-toolbar .button:armed,
.tool-bar.list-related-toolbar .button:selected {
-fx-background-color: COLOR_VGRAD_DARK;
}
.tool-bar.list-related-toolbar .toggle-button:focused,
.tool-bar.list-related-toolbar .button:focused {
-fx-border-style: dotted inside;
-fx-border-color: black;
}
/*******************************************************************************
* *
* PopupMenu *
* *
******************************************************************************/
.context-menu {
-fx-font-size: 12px;
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
-fx-background-insets: 0, 1;
-fx-padding: 1;
-fx-effect: dropshadow(three-pass-box, rgba(0.0, 0.0, 0.0, 0.2), 3, 0, 0, 2);
}
.context-menu > .separator {
-fx-padding: 0.0em 0.333333em 0.0em 0.333333em; /* 0 4 0 4 */
}
.menu-item {
-fx-background-color: transparent;
-fx-padding: 0.4em;
}
.menu-item > .graphic-container {
-fx-padding: 0 6px 0 0;
}
.menu-item > .label {
-fx-text-fill: COLOR_TEXT;
}
.menu-item:focused {
-fx-background-color: COLOR_VGRAD_DARK;
}
.menu-item:disabled > .label {
-fx-text-fill: COLOR_TEXT_DISABLED;
}
/****************************************************************************
* *
* Tooltip *
* *
****************************************************************************/
.tooltip {
-fx-background-color: COLOR_BORDER_DARK, #FE9;
-fx-background-insets: 0, 1;
-fx-padding: 0.4em 0.3em 0.4em 0.3em;
-fx-font-size: 0.8em;
}
/****************************************************************************
* *
* Separator *
* *
****************************************************************************/
.separator .line {
-fx-border-style: solid;
-fx-border-width: 1px;
-fx-background-color: null;
}
.separator:horizontal .line {
-fx-border-color: COLOR_BORDER transparent transparent transparent;
}
/****************************************************************************
* *
* CheckBox *
* *
****************************************************************************/
.check-box {
-fx-label-padding: 0 0 0 6px;
-fx-text-fill: COLOR_TEXT;
}
.check-box > .box {
-fx-padding: 3px;
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
}
.check-box > .box > .mark {
-fx-background-color: transparent;
-fx-padding: 4px;
-fx-shape: "M-1,4, L-1,5.5 L3.5,8.5 L9,0 L9,-1 L7,-1 L3,6 L1,4 Z";
}
/* armed: */
.check-box:armed > .box {
-fx-background-color: COLOR_BORDER_DARK, #FFF;
}
/* selected: */
.check-box:selected > .box > .mark {
-fx-background-color: COLOR_TEXT;
}
/* focused */
.check-box:focused {
-fx-border-style: dotted inside;
}
/* disabled: */
.check-box:disabled > .box {
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
}
.check-box:disabled:selected > .box > .mark {
-fx-background-color: COLOR_TEXT_DISABLED;
}
/*******************************************************************************
* *
* ChoiceBox *
* *
******************************************************************************/
.choice-box {
-fx-background-color: COLOR_BORDER_DARK, COLOR_BACKGROUND;
-fx-background-insets: 0, 1;
-fx-background-radius: 0, 0;
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
-fx-text-fill: COLOR_TEXT;
}
.choice-box:focused {
-fx-border-style: dotted inside;
}
.choice-box > .open-button > .arrow {
-fx-background-color: transparent, COLOR_TEXT;
-fx-background-insets: 0 0 -1 0, 0;
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
.choice-box .context-menu {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
}
/****************************************************************************
* *
* ProgressIndicator *
* *
****************************************************************************/
.progress-indicator {
-fx-indeterminate-segment-count: 12;
-fx-spin-enabled: true;
}
.progress-indicator:indeterminate > .spinner {
-fx-padding: 0.083333em;
}
.progress-indicator:indeterminate .segment {
-fx-background-color: COLOR_BORDER_DARK;
}
.progress-indicator:indeterminate .segment0 {
-fx-shape:"M10,0C9.998,0,9.995,0,9.992,0C9.991,0,9.991,0,9.99,0C9.988,0,9.986,0,9.985,0S9.982,0,9.981,0S9.979,0,9.978,0S9.975,0,9.974,0S9.972,0,9.971,0C9.969,0,9.968,0,9.966,0H9.965c-0.007,0-0.014,0-0.02,0C9.944,0,9.944,0,9.944,0C9.941,0,9.939,0,9.937,0c-0.77,0.007-1.389,0.634-1.384,1.404C8.557,2.176,9.185,2.8,9.956,2.8c0.001,0,0.003,0,0.004,0H10c0.773-0.002,1.4-0.63,1.4-1.404c0-0.77-0.622-1.393-1.392-1.396C10.006,0,10.003,0,10,0L10,0z";
}
.progress-indicator:indeterminate .segment1 {
-fx-shape:"M5.688,1.156c-0.236,0-0.476,0.06-0.696,0.187C4.98,1.349,4.969,1.356,4.958,1.363c0,0-0.001,0-0.001,0C4.955,1.364,4.954,1.365,4.952,1.366c-0.001,0-0.002,0.001-0.004,0.002c0,0,0,0-0.001,0C4.944,1.371,4.94,1.373,4.936,1.375c0,0,0,0-0.001,0C4.933,1.377,4.931,1.378,4.929,1.38C4.267,1.772,4.046,2.624,4.438,3.288c0.261,0.444,0.73,0.692,1.212,0.692c0.24,0,0.484-0.062,0.706-0.192l0.034-0.02C7.058,3.378,7.283,2.52,6.896,1.851C6.636,1.405,6.168,1.156,5.688,1.156L5.688,1.156z";
}
.progress-indicator:indeterminate .segment2 {
-fx-shape:"M2.534,4.326c-0.482,0-0.951,0.25-1.209,0.697C1.323,5.027,1.321,5.029,1.32,5.031l0,0C1.319,5.033,1.318,5.034,1.317,5.036S1.315,5.039,1.314,5.04c0,0.001,0,0.002-0.001,0.002C1.312,5.044,1.311,5.046,1.31,5.048c0,0,0,0,0,0.001C1.309,5.051,1.308,5.053,1.307,5.055C1.302,5.063,1.297,5.071,1.292,5.079l0,0C1.291,5.082,1.29,5.084,1.288,5.087c-0.376,0.67-0.141,1.519,0.529,1.898c0.218,0.123,0.456,0.182,0.69,0.182c0.488,0,0.963-0.255,1.222-0.708l0.02-0.035c0.383-0.671,0.149-1.527-0.521-1.912C3.008,4.386,2.769,4.326,2.534,4.326L2.534,4.326z";
}
.progress-indicator:indeterminate .segment3 {
-fx-shape:"M1.396,8.648c-0.002,0-0.005,0-0.008,0C0.619,8.652-0.001,9.278,0,10.047c0,0.002,0,0.006,0,0.008l0,0c0,0.019,0,0.037,0,0.056c0,0.001,0,0.002,0,0.003s0,0.003,0,0.004c0.01,0.765,0.632,1.378,1.396,1.378c0.005,0,0.01,0,0.015,0c0.773-0.009,1.395-0.642,1.389-1.415v-0.04C2.794,9.27,2.166,8.648,1.396,8.648L1.396,8.648z";
}
.progress-indicator:indeterminate .segment4 {
-fx-shape:"M2.579,12.955c-0.242,0-0.487,0.062-0.71,0.194c-0.664,0.391-0.885,1.242-0.499,1.906c0.013,0.021,0.025,0.043,0.038,0.063c0.262,0.436,0.724,0.678,1.197,0.678c0.243,0,0.49-0.063,0.714-0.197c0.665-0.396,0.883-1.257,0.489-1.922l-0.02-0.034C3.526,13.201,3.059,12.955,2.579,12.955L2.579,12.955z";
}
.progress-indicator:indeterminate .segment5 {
-fx-shape:"M5.772,16.09c-0.489,0-0.965,0.257-1.223,0.712c-0.38,0.671-0.146,1.52,0.523,1.901c0.002,0.001,0.004,0.002,0.007,0.004h0c0.004,0.002,0.008,0.004,0.012,0.007c0,0,0,0,0.001,0c0.001,0.001,0.002,0.002,0.004,0.002c0.001,0.001,0.003,0.002,0.004,0.003c0,0,0.001,0,0.001,0.001c0.012,0.006,0.023,0.013,0.035,0.019c0.214,0.119,0.446,0.176,0.675,0.176c0.489,0,0.963-0.258,1.22-0.716c0.377-0.675,0.137-1.529-0.537-1.908l-0.035-0.02C6.242,16.149,6.006,16.09,5.772,16.09L5.772,16.09z";
}
.progress-indicator:indeterminate .segment6 {
-fx-shape:"M10.14,17.198c-0.006,0-0.013,0-0.02,0h-0.039c-0.773,0.011-1.394,0.646-1.385,1.419c0.008,0.767,0.631,1.382,1.396,1.382c0.003,0,0.006,0,0.009-0.001c0.024,0,0.051,0,0.075-0.001c0.769-0.016,1.38-0.648,1.367-1.418C11.53,17.813,10.904,17.198,10.14,17.198L10.14,17.198z";
}
.progress-indicator:indeterminate .segment7 {
-fx-shape:"M14.433,15.97c-0.245,0-0.494,0.064-0.72,0.2l-0.034,0.021c-0.663,0.397-0.88,1.258-0.483,1.922c0.261,0.439,0.725,0.683,1.2,0.683c0.24,0,0.484-0.062,0.707-0.194c0.022-0.013,0.044-0.025,0.065-0.039c0.656-0.399,0.866-1.254,0.469-1.913C15.373,16.212,14.909,15.97,14.433,15.97L14.433,15.97z";
}
.progress-indicator:indeterminate .segment8 {
-fx-shape:"M17.539,12.748c-0.493,0-0.973,0.261-1.229,0.723l-0.02,0.034c-0.376,0.676-0.133,1.53,0.542,1.907c0.216,0.121,0.45,0.178,0.681,0.178c0.487,0,0.96-0.256,1.217-0.71c0.003-0.006,0.007-0.012,0.01-0.019c0.007-0.013,0.015-0.025,0.021-0.038l0,0c0.002-0.003,0.003-0.005,0.004-0.008c0.369-0.675,0.124-1.521-0.55-1.893C18.001,12.805,17.769,12.748,17.539,12.748L17.539,12.748z";
}
.progress-indicator:indeterminate .segment9 {
-fx-shape:"M18.603,8.408c-0.011,0-0.021,0-0.031,0c-0.773,0.018-1.388,0.657-1.373,1.431l0.001,0.04c0.015,0.765,0.641,1.377,1.403,1.377c0.008,0,0.016,0,0.023,0c0.77-0.013,1.383-0.646,1.373-1.414c0-0.003,0-0.006,0-0.009l0,0c-0.001-0.019-0.001-0.037-0.001-0.055c0-0.001,0-0.001-0.001-0.002c0-0.002,0-0.004,0-0.006C19.979,9.012,19.358,8.408,18.603,8.408L18.603,8.408z";
}
.progress-indicator:indeterminate .segment10 {
-fx-shape:"M17.345,4.121c-0.248,0-0.5,0.066-0.728,0.205c-0.659,0.403-0.869,1.266-0.468,1.927l0.021,0.034c0.265,0.435,0.728,0.675,1.202,0.675c0.247,0,0.497-0.065,0.724-0.202c0.659-0.397,0.871-1.252,0.477-1.912c-0.007-0.011-0.013-0.021-0.02-0.032c-0.001-0.002-0.002-0.003-0.002-0.004c-0.001,0-0.001-0.001-0.001-0.002c-0.004-0.005-0.008-0.011-0.011-0.017c0-0.001,0-0.001-0.001-0.001c-0.001-0.002-0.002-0.004-0.004-0.007C18.271,4.358,17.813,4.121,17.345,4.121L17.345,4.121z";
}
.progress-indicator:indeterminate .segment11 {
-fx-shape:"M14.104,1.039c-0.494,0-0.974,0.264-1.227,0.729c-0.37,0.679-0.12,1.531,0.559,1.903l0.034,0.019c0.214,0.117,0.445,0.173,0.673,0.173c0.495,0,0.976-0.262,1.231-0.726c0.371-0.674,0.129-1.519-0.542-1.894c-0.012-0.006-0.024-0.013-0.036-0.02c-0.007-0.004-0.014-0.008-0.021-0.012c-0.003-0.001-0.006-0.003-0.009-0.005C14.556,1.094,14.329,1.039,14.104,1.039L14.104,1.039z";
}
/*****************************************************************************
* *
* I/O Chart *
* *
*****************************************************************************/
.chart {
-fx-padding: 5px;
}
/* legend */
.chart-legend {
-fx-text-fill: COLOR_TEXT;
-fx-background-color: transparent;
-fx-padding: 0.4em;
}
.chart-line-symbol {
-fx-background-radius: 5px;
-fx-padding: 5px;
}
.default-color0.chart-line-symbol { -fx-background-color: COLOR_CHART_GREEN; }
.default-color1.chart-line-symbol { -fx-background-color: COLOR_CHART_RED; }
/* axis */
.axis {
-fx-tick-label-font-size: 0.8em;
-fx-tick-label-fill: COLOR_TEXT;
}
.axis-label {
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0 0 0.8em 0;
}
.axis:left {
-fx-border-color: transparent COLOR_BORDER transparent transparent;
}
.axis-tick-mark,
.axis-minor-tick-mark {
-fx-fill: null;
-fx-stroke: #CCC;
}
/* content */
.chart-content {
-fx-padding: 10px;
}
.chart-horizontal-grid-lines {
-fx-stroke: COLOR_BORDER;
}
.chart-alternative-column-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-alternative-row-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-vertical-zero-line,
.chart-horizontal-zero-line {
-fx-stroke: COLOR_BORDER;
}
.chart-series-line {
-fx-stroke-width: 2px;
}
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
/*******************************************************************************
* *
* Dialog *
* *
******************************************************************************/
.dialog-pane {
-fx-background-color: COLOR_BACKGROUND;
-fx-padding: 20px 20px 20px 96px;
-fx-background-image: url("/img/dialog-appicon.png");
-fx-background-repeat: no-repeat;
-fx-background-position: 20px 20px;
}
/* HEADER */
.dialog-pane:header .header-panel {
-fx-padding: 0 0 12px 0;
}
/* TITLE */
.dialog-pane:header .header-panel .label {
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-font-size: 14px;
}
/* CONTENT LABEL */
.dialog-pane > .content {
-fx-alignment: top-left;
-fx-wrap-text: true;
-fx-font-size: 12px;
}
/* BUTTONS */
.dialog-pane > .button-bar > .container {
-fx-padding: 20px 0 0 0;
}
.alert.confirmation.dialog-pane,
.text-input-dialog.dialog-pane,
.choice-dialog.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-confirm.png");
}
.alert.information.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-information.png");
}
.alert.error.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-error.png");
}
.alert.warning.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-warning.png");
}

View File

@@ -1,656 +0,0 @@
/*
* Copyright (c) 2015 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
* Jean-Noël Charon - implementation of the dialog css
*
*/
.root {
-fx-font-family: 'lucida-grande', sans-serif;
-fx-font-smoothing-type: lcd;
-fx-font-size: 13px;
COLOR_TEXT: #000;
COLOR_TEXT_LIGHT: #888;
COLOR_TEXT_DISABLED: #7B7B7B;
COLOR_HYPERLINK: #0069D9;
COLOR_BORDER: #C8C8C8;
COLOR_BORDER_DARK: #B5B5B5;
COLOR_BACKGROUND: #ECECEC;
COLOR_HGRAD_BTN_BORDER: linear-gradient(to bottom, #C1C1C1 0%, #A6A6A6 100%);
COLOR_HGRAD_BTN_BORDER_DIS: linear-gradient(to bottom, #D2D2D2 0%, #C4C4C4 100%);
COLOR_HGRAD_BTN_DEF_BORDER: linear-gradient(to bottom, #4AA0F9 0%, #045FFF 100%);
COLOR_HGRAD_BTN_DEF_BACKGROUND: linear-gradient(to bottom, #69B2FA 0%, #0D81FF 100%);
COLOR_HGRAD_BTN_ARMED_BORDER: linear-gradient(to bottom, #237FFE 0%, #023FDD 100%);
COLOR_HGRAD_BTN_ARMED_BACKGROUND: linear-gradient(to bottom, #4A97FD 0%, #0867E4 100%);
COLOR_CHART_GREEN: #28CA40;
COLOR_CHART_RED: #FD4943;
-fx-background-color: COLOR_BACKGROUND;
-fx-text-fill: COLOR_TEXT;
}
/****************************************************************************
* *
* Labels & Fonts *
* *
****************************************************************************/
.label {
-fx-text-fill: COLOR_TEXT;
}
.ionicons {
-fx-font-family: Ionicons;
}
.fontawesome {
-fx-font-family: FontAwesome;
}
.caption-label {
-fx-font-size: 0.9em;
}
/****************************************************************************
* *
* Hyperlinks *
* *
****************************************************************************/
.hyperlink {
-fx-cursor: hand;
-fx-text-fill: COLOR_HYPERLINK;
}
.hyperlink:hover {
-fx-underline: true;
}
/****************************************************************************
* *
* Buttons *
* *
****************************************************************************/
.button,
.toggle-button {
-fx-pref-height: 21px;
-fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFF;
-fx-background-insets: 0, 1;
-fx-background-radius: 4;
-fx-padding: 2px 12px 3px 12px;
-fx-text-fill: COLOR_TEXT;
-fx-alignment: CENTER;
-fx-focus-traversable: false;
-fx-effect: dropshadow(one-pass-box, #DCDCDC, 0.0, 0.0, 0.0, 1.0);
}
.root.active-window .button:default {
-fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND;
-fx-text-fill: #FFF;
}
.button:disabled,
.button:default:disabled,
.toggle-button:disabled,
.root.active-window .button:default:disabled {
-fx-background-color: COLOR_HGRAD_BTN_BORDER_DIS, #F2F2F2;
-fx-background-insets: 0, 1;
-fx-text-fill: COLOR_TEXT_DISABLED;
-fx-effect: dropshadow(one-pass-box, #E0E0E0, 0.0, 0.0, 0.0, 0.5);
}
.button:armed,
.root.active-window .button:default:armed {
-fx-background-color: COLOR_HGRAD_BTN_ARMED_BORDER, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
-fx-text-fill: #FFF;
}
.toggle-button:armed,
.toggle-button:selected {
-fx-background-color: linear-gradient(to bottom, #C0C0C0 0%, #ADADAD 100%);
}
/****************************************************************************
* *
* Segmented Buttons *
* *
****************************************************************************/
.segmented-button-bar .button,
.segmented-button-bar .toggle-button {
-fx-background-radius: 0;
-fx-background-insets: 0, 1 1 1 0;
}
.segmented-button-bar .button.first,
.segmented-button-bar .toggle-button.first {
-fx-background-radius: 4 0 0 4;
-fx-background-insets: 0, 1;
}
.segmented-button-bar .button.last,
.segmented-button-bar .toggle-button.last {
-fx-background-radius: 0 4 4 0;
}
/****************************************************************************
* *
* Text Fields *
* *
****************************************************************************/
.text-input {
-fx-text-fill: COLOR_TEXT;
-fx-highlight-fill: derive(#FFF,-20%);
-fx-highlight-text-fill: COLOR_TEXT;
-fx-prompt-text-fill: derive(#FFF,-30%);
-fx-border-color: COLOR_BORDER_DARK;
-fx-border-width: 1px;
-fx-background-color: #FFFFFF;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-cursor: text;
-fx-padding: 2px;
}
.text-input:focused {
-fx-highlight-fill: #B2D7FF;
-fx-border-color: #78A6D7;
-fx-border-width: 1px;
-fx-background-color: #8FBDEE, #FFFFFF;
-fx-background-insets: -3, 0;
-fx-background-radius: 3, 0;
-fx-prompt-text-fill: transparent;
}
.text-input:disabled {
-fx-text-fill: COLOR_TEXT_DISABLED;
-fx-border-color: #CDCDCD;
}
/****************************************************************************
* *
* Vault List *
* *
****************************************************************************/
.list-view {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
-fx-padding: 1;
}
.list-view > .virtual-flow > .scroll-bar:vertical {
-fx-background-insets: 0, 0 0 0 1;
-fx-padding: -1 -1 -1 0;
}
.list-view > .virtual-flow > .scroll-bar:horizontal {
-fx-background-insets: 0, 1 0 0 0;
-fx-padding: 0 -1 -1 -1;
}
.list-view > .virtual-flow > .corner {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1 0 0 1;
}
.list-cell {
-fx-background-color: #FFF;
-fx-padding: 6px;
-fx-text-fill: COLOR_TEXT;
-fx-opacity: 1;
}
.root.active-window .list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused,
.root.active-window .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected,
.root.active-window .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover {
-fx-background-color: #0069D9;
-fx-text-fill: #FFF;
}
.root.inactive-window .list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused,
.root.inactive-window .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected,
.root.inactive-window .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover {
-fx-background-color: #DCDCDC;
-fx-text-fill: #FFF;
}
.list-cell .detail-label {
-fx-text-fill: COLOR_TEXT_LIGHT;
-fx-font-size: 0.8em;
}
.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused .detail-label,
.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected .detail-label,
.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover .detail-label{
-fx-text-fill: #FFF;
}
/*******************************************************************************
* *
* ScrollBar *
* *
******************************************************************************/
.scroll-bar:horizontal,
.scroll-bar:vertical {
-fx-background-color: #E8E8E8, #FAFAFA;
}
.scroll-bar:disabled {
-fx-opacity: 0.6;
}
.scroll-bar > .thumb {
-fx-background-color: #C1C1C1;
-fx-background-insets: 2px;
-fx-background-radius: 4px;
}
.scroll-bar > .thumb:hover {
-fx-background-color: #7D7D7D;
}
.scroll-bar > .increment-button,
.scroll-bar > .decrement-button {
-fx-background-color: transparent;
-fx-color: transparent;
}
.scroll-bar:horizontal > .increment-button,
.scroll-bar:horizontal > .decrement-button {
-fx-padding: 6 0 6 0;
}
.scroll-bar:vertical > .increment-button,
.scroll-bar:vertical > .decrement-button {
-fx-padding: 0 6 0 6;
}
/****************************************************************************
* *
* Vault List Toolbar *
* *
****************************************************************************/
.tool-bar.list-related-toolbar {
-fx-font-size: 1.4em;
-fx-background-color: COLOR_BORDER, #F7F7F7;
-fx-background-insets: 0, 0 1 1 1;
-fx-padding: 0;
-fx-spacing: 0;
-fx-alignment: CENTER_LEFT;
}
.tool-bar.list-related-toolbar .spacer {
-fx-border-color: transparent COLOR_BORDER transparent transparent;
-fx-border-width: 1;
}
.tool-bar.list-related-toolbar .toggle-button,
.tool-bar.list-related-toolbar .button {
-fx-pref-height: 25px;
-fx-text-fill: COLOR_TEXT;
-fx-background-color: transparent;
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-border-color: transparent COLOR_BORDER transparent transparent;
-fx-border-width: 1;
}
.tool-bar.list-related-toolbar .toggle-button:disabled,
.tool-bar.list-related-toolbar .button:disabled {
-fx-text-fill: COLOR_TEXT_DISABLED;
}
.tool-bar.list-related-toolbar .toggle-button:armed,
.tool-bar.list-related-toolbar .toggle-button:selected,
.tool-bar.list-related-toolbar .button:armed,
.tool-bar.list-related-toolbar .button:selected {
-fx-background-color: linear-gradient(to bottom, #C0C0C0 0%, #ADADAD 100%);
}
/*******************************************************************************
* *
* PopupMenu *
* *
******************************************************************************/
.context-menu {
-fx-font-size: 13px;
-fx-background-color: rgba(255.0, 255.0, 255.0, 0.9);
-fx-background-insets: 0;
-fx-background-radius: 4.0;
-fx-padding: 4px 0 4px 0;
-fx-effect: dropshadow(three-pass-box, rgba(0.0,0.0,0.0,0.6), 8.0, 0.0, 0.0, 0.0 );
}
.context-menu > .separator {
-fx-padding: 0.0em 0.333333em 0.0em 0.333333em; /* 0 4 0 4 */
}
.menu-item {
-fx-background-color: transparent;
-fx-background-insets:0.0;
-fx-padding:0.2em 1em 0.2em 1em;
-fx-border-width: 0.0 0.0 0.0 0.0;
-fx-border-color:transparent;
}
.menu-item > .graphic-container {
-fx-padding: 1px 6px 0 0;
}
.menu-item > .label {
-fx-padding: 0em 0.5em 0em 0em;
-fx-text-fill: COLOR_TEXT;
}
.menu-item:disabled > .label {
-fx-opacity: 0.6;
}
.menu-item:focused {
-fx-background: #B2D7FF;
-fx-background-color: #2283FB;
-fx-text-fill: #FFF;
}
.menu-item:focused > .label {
-fx-text-fill: white;
}
.menu-item:disabled {
-fx-opacity: 0.6;
}
/*******************************************************************************
* *
* Tooltip *
* *
******************************************************************************/
.tooltip {
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
-fx-padding: 0.2em 0.4em 0.2em 0.4em;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 2, 0, 0, 0);
-fx-font-size: 0.8em;
}
/****************************************************************************
* *
* Separator *
* *
****************************************************************************/
.separator .line {
-fx-border-style: solid;
-fx-border-width: 1px;
-fx-background-color: null;
}
.separator:horizontal .line {
-fx-border-color: COLOR_BORDER transparent transparent transparent;
}
/*******************************************************************************
* *
* CheckBox *
* *
******************************************************************************/
.check-box {
-fx-label-padding: 0 0 0 6px;
-fx-text-fill: COLOR_TEXT;
}
.check-box > .box {
-fx-padding: 3px;
-fx-background-color: linear-gradient(to bottom, #A5A5A5 0%, #B8B8B8 100%), #F3F3F3, #FFFFFF;
-fx-background-radius: 2.5, 2.5, 2.5;
-fx-background-insets: 0, 1, 2 1 1 1;
}
.check-box > .box > .mark {
-fx-background-color: transparent;
-fx-padding: 4px;
-fx-shape: "M-1,4, L-1,5.5 L3.5,8.5 L9,0 L9,-1 L7,-1 L3,6 L1,4 Z";
}
/* selected: */
.root.active-window .check-box:selected > .box {
-fx-background-color: #2C90FC, #3B99FC;
-fx-background-insets: 0, 1;
}
.root.active-window .check-box:selected > .box > .mark {
-fx-background-color: white;
}
.root.inactive-window .check-box:selected > .box > .mark {
-fx-background-color: #444444;
}
/* disabled: */
.check-box:disabled {
-fx-text-fill: COLOR_TEXT_DISABLED;
}
.root.active-window .check-box:disabled > .box,
.root.active-window .check-box:disabled:selected > .box,
.root.inactive-window .check-box:disabled > .box,
.root.inactive-window .check-box:disabled:selected > .box {
-fx-background-color: linear-gradient(to bottom, #BBBBBB 0%, #C2C2C2 100%), #E9E9E9, #F1F1F1;
}
.root.active-window .check-box:disabled:selected > .box > .mark,
.root.inactive-window .check-box:disabled:selected > .box > .mark {
-fx-background-color: COLOR_TEXT_DISABLED;
}
/*******************************************************************************
* *
* ChoiceBox *
* *
******************************************************************************/
.choice-box {
-fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF;
-fx-background-insets: 0, 1;
-fx-background-radius: 4;
-fx-padding: 0 0 0 0.8em;
-fx-text-fill: COLOR_TEXT;
-fx-alignment: CENTER;
-fx-effect: dropshadow(one-pass-box, #DCDCDC, 0.0, 0.0, 0.0, 1.0);
}
.choice-box > .open-button {
-fx-background-insets: 0, 1 1 1 0;
-fx-background-radius: 0 4 4 0;
-fx-padding: 4 5 4 4;
}
.root.active-window .choice-box > .open-button {
-fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND;
}
.root.inactive-window .choice-box > .open-button {
-fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF;
}
.choice-box > .open-button > .arrow {
-fx-shape: "M 0 5 L 4 0 L 8 5 L 6 5 L 4 2 L 2 5 Z M 0 8 L 4 13 L 8 8 L 6 8 L 4 11 L 2 8 Z";
-fx-scale-shape: true;
-fx-min-width: 9px;
-fx-min-height: 13px;
}
.root.active-window .choice-box > .open-button > .arrow {
-fx-background-color: #FFFFFF;
}
.root.inactive-window .choice-box > .open-button > .arrow {
-fx-background-color: #444444;
}
.choice-box .context-menu {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
}
/****************************************************************************
* *
* ProgressIndicator *
* Derived from aquafx-project.com, (C) Claudine Zillmann, see NOTICE.md *
* *
****************************************************************************/
.progress-indicator {
-fx-indeterminate-segment-count: 12;
-fx-spin-enabled: true;
}
.progress-indicator:indeterminate > .spinner {
-fx-padding: 0.083333em;
}
.progress-indicator:indeterminate .segment {
-fx-background-color: rgb(95.0, 95.0, 98.0), rgb(122.0, 122.0, 125.0);
-fx-background-insets:0.0, 0.5;
}
.progress-indicator:indeterminate .segment0 {
-fx-shape:"m 12.007729,4.9541827 c -0.49762,0.7596865 0.893181,1.6216808 1.327833,0.7666252 L 15.456199,2.0477574 C 15.942094,1.2061627 14.61426,0.43953765 14.128365,1.2811324 z";
}
.progress-indicator:indeterminate .segment1 {
-fx-shape:"m 9.2224559,4.62535 c -0.051108,0.9067177 1.5843581,0.957826 1.5332501,0 l 0,-4.24127319 c 0,-0.9717899 -1.5332501,-0.9717899 -1.5332501,0 z";
}
.progress-indicator:indeterminate .segment2 {
-fx-shape:"M 8.0465401,4.9030617 C 8.5441601,5.6627485 7.1533584,6.5247425 6.7187068,5.6696872 L 4.5980702,1.9966363 C 4.1121752,1.1550418 5.4400085,0.38841683 5.9259035,1.2300114 z";
}
.progress-indicator:indeterminate .segment3 {
-fx-shape:"M 5.7330066,6.5305598 C 6.5579512,6.9103162 5.8366865,8.3790371 5.0144939,7.8850315 L 1.2677551,5.8974832 C 0.409277,5.4420823 1.1277888,4.0876101 1.9862674,4.5430105 z";
}
.progress-indicator:indeterminate .segment4 {
-fx-shape:"m 0.42171041,9.2083842 c -0.90671825,-0.051108 -0.95782608,1.5843588 0,1.5332498 l 4.24127319,0 c 0.9717899,0 0.9717899,-1.5332498 0,-1.5332498 z";
}
.progress-indicator:indeterminate .segment5 {
-fx-shape:"M 5.7330066,13.443113 C 6.5579512,13.063356 5.8366865,11.594635 5.0144939,12.088641 L 1.2677551,14.076189 C 0.409277,14.53159 1.1277888,15.886062 1.9862674,15.430662 z";
}
.progress-indicator:indeterminate .segment6 {
-fx-shape:"M 8.0465401,15.070611 C 8.5441601,14.310924 7.1533584,13.44893 6.7187068,14.303985 l -2.1206366,3.673051 c -0.485895,0.841595 0.8419383,1.60822 1.3278333,0.766625 z";
}
.progress-indicator:indeterminate .segment7 {
-fx-shape:"m 9.2224559,19.539943 c -0.051108,0.906718 1.5843581,0.957826 1.5332501,0 l 0,-4.241273 c 0,-0.97179 -1.5332501,-0.97179 -1.5332501,0 z";
}
.progress-indicator:indeterminate .segment8 {
-fx-shape:"m 12.10997,15.070611 c -0.49762,-0.759687 0.893182,-1.621681 1.327834,-0.766626 l 2.120636,3.673051 c 0.485895,0.841595 -0.841938,1.60822 -1.327833,0.766625 z";
}
.progress-indicator:indeterminate .segment9 {
-fx-shape:"m 14.423504,13.443113 c -0.824945,-0.379757 -0.10368,-1.848478 0.718512,-1.354472 l 3.746739,1.987548 c 0.858478,0.455401 0.139967,1.809873 -0.718512,1.354473 z";
}
.progress-indicator:indeterminate .segment10 {
-fx-shape:"m 15.372451,9.2445322 c -0.906719,-0.051108 -0.957826,1.5843588 0,1.5332498 l 4.241273,0 c 0.97179,0 0.97179,-1.5332498 0,-1.5332498 z";
}
.progress-indicator:indeterminate .segment11 {
-fx-shape:"m 14.321262,6.5816808 c -0.824944,0.3797564 -0.10368,1.8484772 0.718513,1.3544717 L 18.786514,5.9486042 C 19.644992,5.4932031 18.92648,4.1387308 18.068001,4.5941315 z";
}
/*****************************************************************************
* *
* I/O Chart *
* *
*****************************************************************************/
.chart {
-fx-padding: 5px;
}
/* legend */
.chart-legend {
-fx-text-fill: COLOR_TEXT;
-fx-background-color: transparent;
-fx-padding: 0.4em;
}
.chart-line-symbol {
-fx-background-color: #000, #FFF;
-fx-background-insets: 0, 2;
-fx-background-radius: 5px;
-fx-padding: 5px;
}
.default-color0.chart-line-symbol { -fx-background-color: COLOR_CHART_GREEN, #FFF; }
.default-color1.chart-line-symbol { -fx-background-color: COLOR_CHART_RED, #FFF; }
/* axis */
.axis {
-fx-tick-label-font-size: 0.8em;
-fx-tick-label-fill: COLOR_TEXT;
}
.axis-label {
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0 0 0.8em 0;
}
.axis:left {
-fx-border-color: transparent COLOR_BORDER transparent transparent;
}
.axis-tick-mark,
.axis-minor-tick-mark {
-fx-fill: null;
-fx-stroke: #CCC;
}
/* content */
.chart-content {
-fx-padding: 10px;
}
.chart-horizontal-grid-lines {
-fx-stroke: COLOR_BORDER;
}
.chart-alternative-column-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-alternative-row-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-vertical-zero-line,
.chart-horizontal-zero-line {
-fx-stroke: COLOR_BORDER;
}
.chart-series-line {
-fx-stroke-width: 2px;
}
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
/*******************************************************************************
* *
* Dialog *
* *
******************************************************************************/
.dialog-pane {
-fx-background-color: COLOR_BACKGROUND;
-fx-padding: 20px 20px 20px 96px;
-fx-background-image: url("/img/dialog-appicon.png");
-fx-background-repeat: no-repeat;
-fx-background-position: 20px 20px;
}
/* HEADER */
.dialog-pane:header .header-panel {
-fx-padding: 0 0 12px 0;
}
/* TITLE */
.dialog-pane:header .header-panel .label {
-fx-font-weight: bold;
-fx-wrap-text: true;
}
/* CONTENT LABEL */
.dialog-pane > .content {
-fx-alignment: top-left;
-fx-wrap-text: true;
-fx-font-size: 11px;
-fx-line-spacing: 1.0;
}
/* BUTTONS */
.dialog-pane > .button-bar > .container {
-fx-padding: 12px 0 0 0;
}
.dialog-pane > .button-bar .button:default {
-fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND;
-fx-text-fill: #FFF;
}
.dialog-pane > .button-bar .button:default:armed {
-fx-background-color: COLOR_HGRAD_BTN_ARMED_BORDER, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
-fx-text-fill: #FFF;
}

View File

@@ -1,632 +0,0 @@
/*
* Copyright (c) 2015 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
* Jean-Noël Charon - implementation of the dialog css
*
*/
.root {
-fx-font-family: 'Segoe UI Semibold';
-fx-font-smoothing-type: lcd;
-fx-font-size: 12px;
COLOR_TEXT: #000;
COLOR_TEXT_DISABLED: #888;
COLOR_HYPERLINK: #3399FF;
COLOR_BORDER: #ACACAC;
COLOR_BORDER_FOCUS: #3399FF;
COLOR_CONTROL_BASE: #EAEAEA;
COLOR_BACKGROUND: #F0F0F0;
COLOR_CHART_GREEN: #A1CD5f;
COLOR_CHART_RED: #C75050;
COLOR_HGRAD_BTN_BACKGROUND: linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
COLOR_HGRAD_BTN_DISABLED_BORDER: linear-gradient(to bottom, #D2D2D2 0%, #C4C4C4 100%);
COLOR_HGRAD_BTN_ARMED_BACKGROUND: linear-gradient(to bottom, #DAECFC 0%, #C4E0FC 100%);
-fx-background-color: COLOR_BACKGROUND;
-fx-text-fill: COLOR_TEXT;
}
/****************************************************************************
* *
* Labels & Fonts *
* *
****************************************************************************/
.label {
-fx-text-fill: COLOR_TEXT;
}
.ionicons {
-fx-font-family: Ionicons;
}
.fontawesome {
-fx-font-family: FontAwesome;
}
.caption-label {
-fx-font-size: 0.9em;
}
/****************************************************************************
* *
* Hyperlinks *
* *
****************************************************************************/
.hyperlink {
-fx-cursor: hand;
-fx-text-fill: COLOR_HYPERLINK;
}
.hyperlink:hover {
-fx-underline: true;
}
/****************************************************************************
* *
* Buttons *
* *
****************************************************************************/
.button,
.toggle-button {
-fx-pref-height: 27px;
-fx-background-color: COLOR_BORDER, COLOR_HGRAD_BTN_BACKGROUND;
-fx-background-insets: 0, 1;
-fx-padding: 2px 12px 2px 12px;
-fx-text-fill: COLOR_TEXT;
-fx-alignment: CENTER;
-fx-border-color: transparent;
-fx-border-insets: 2px;
}
.button:default {
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_BACKGROUND;
}
.button:disabled,
.button:default:disabled {
-fx-background-color: COLOR_HGRAD_BTN_DISABLED_BORDER, #F2F2F2;
-fx-text-fill: #8B8B8B;
}
.button:armed,
.button:default:armed,
.toggle-button:armed,
.toggle-button:selected {
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
}
.button:focused,
.toggle-button:focused {
-fx-border-color: black;
-fx-border-insets: 2px;
-fx-border-style: dotted inside;
}
/****************************************************************************
* *
* Segmented Buttons *
* *
****************************************************************************/
.segmented-button-bar .button,
.segmented-button-bar .toggle-button {
-fx-background-insets: 0, 1;
}
.segmented-button-bar .button.last,
.segmented-button-bar .toggle-button.last {
-fx-background-insets: 0, 1 1 1 0;
}
/****************************************************************************
* *
* Text Fields *
* *
****************************************************************************/
.text-input {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0.4em;
-fx-cursor: text;
}
.text-input:focused {
-fx-background-color: COLOR_BORDER_FOCUS, #FFF;
}
.text-input:disabled {
-fx-background-color: COLOR_BORDER, #F1F1F1;
-fx-text-fill: COLOR_TEXT_DISABLED;
}
/****************************************************************************
* *
* Vault List *
* *
****************************************************************************/
.list-view {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-border-style: dotted inside;
-fx-border-color: transparent;
-fx-background-insets: 0, 1;
-fx-padding: 1;
}
.list-view:focused {
-fx-border-color: black;
}
.list-view > .virtual-flow > .scroll-bar:vertical{
-fx-background-insets: 0, 0 0 0 1;
-fx-padding: -1 -1 -1 0;
}
.list-view > .virtual-flow > .scroll-bar:horizontal{
-fx-background-insets: 0, 1 0 0 0;
-fx-padding: 0 -1 -1 -1;
}
.list-view > .virtual-flow > .corner {
-fx-background-color: COLOR_BORDER, COLOR_CONTROL_BASE;
-fx-background-insets: 0, 1 0 0 1;
}
.list-cell {
-fx-background-color: #FFF;
-fx-padding: 0.6em;
-fx-text-fill: COLOR_TEXT;
-fx-opacity: 1;
}
.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused {
-fx-background-color: COLOR_BORDER_FOCUS, #FFF;
-fx-background-insets: 0, 1;
}
.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected {
-fx-background-color: #DEDEDE, #F7F7F7;
-fx-background-insets: 0, 1;
}
.list-cell:filled:hover {
-fx-background-color: #F7F7F7;
}
.list-cell .detail-label {
-fx-text-fill: COLOR_TEXT_DISABLED;
-fx-font-size: 0.8em;
}
/*******************************************************************************
* *
* ScrollBar *
* *
******************************************************************************/
.scroll-bar:horizontal,
.scroll-bar:vertical {
-fx-background-color: COLOR_CONTROL_BASE;
}
.scroll-bar:disabled {
-fx-opacity: 0.6;
}
.scroll-bar > .thumb {
-fx-background-color: #CDCDCD;
}
.scroll-bar > .thumb:hover {
-fx-background-color: #A6A6A6;
}
.scroll-bar > .increment-button,
.scroll-bar > .decrement-button {
-fx-background-color: transparent;
-fx-color: transparent;
}
.scroll-bar:horizontal > .increment-button,
.scroll-bar:horizontal > .decrement-button,
.scroll-bar:vertical > .increment-button,
.scroll-bar:vertical > .decrement-button {
-fx-padding: 5px 5px;
}
.scroll-bar > .increment-button,
.scroll-bar > .decrement-button {
-fx-background-color: derive(COLOR_CONTROL_BASE,-23%), linear-gradient(to bottom, derive(COLOR_CONTROL_BASE,75%), derive(COLOR_CONTROL_BASE,2%)), linear-gradient(to bottom, derive(COLOR_CONTROL_BASE,10%) ,derive(COLOR_CONTROL_BASE,-6%));
-fx-background-insets: 0, 1, 2;
-fx-color: transparent;
-fx-padding: 3px;
}
.scroll-bar > .increment-button > .increment-arrow,
.scroll-bar > .decrement-button > .decrement-arrow,
.scroll-bar > .increment-button:hover > .increment-arrow,
.scroll-bar > .decrement-button:hover > .decrement-arrow,
.scroll-bar > .increment-button:pressed > .increment-arrow,
.scroll-bar > .decrement-button:pressed > .decrement-arrow {
-fx-background-color: #606060;
}
.scroll-bar:horizontal > .increment-button > .increment-arrow {
-fx-padding: 9 7 0 0;
-fx-shape: "M0.315,1.457l1.414-1.414L5.686,4L1.729,7.957L0.315,6.543L2.857,4L0.315,1.457z";
}
.scroll-bar:vertical > .increment-button > .increment-arrow {
-fx-padding: 7 9 0 0 ;
-fx-shape: "M6.543,0.315l1.414,1.414L4,5.686L0.043,1.729l1.414-1.414L4,2.858L6.543,0.315z";
}
.scroll-bar:horizontal > .decrement-button > .decrement-arrow {
-fx-padding: 9 7 0 0;
-fx-shape: "M5.686,6.543L4.271,7.957L0.314,4l3.957-3.957l1.414,1.414L3.143,4L5.686,6.543z";
}
.scroll-bar:vertical > .decrement-button > .decrement-arrow {
-fx-padding: 7 9 0 0;
-fx-shape: "M1.457,5.563L0.042,4.149L4,0.193l3.957,3.957L6.543,5.563L4,3.021L1.457,5.563z";
}
/****************************************************************************
* *
* Vault List Toolbar *
* *
****************************************************************************/
.tool-bar.list-related-toolbar {
-fx-background-color: transparent;
-fx-padding: 0.4em 0 0 0;
-fx-spacing: 1px;
-fx-alignment: CENTER_LEFT;
}
.tool-bar.list-related-toolbar .toggle-button,
.tool-bar.list-related-toolbar .button {
-fx-font-size: 1.4em;
-fx-text-fill: COLOR_TEXT;
}
.tool-bar.list-related-toolbar .toggle-button:disabled,
.tool-bar.list-related-toolbar .button:disabled {
-fx-text-fill: COLOR_TEXT_DISABLED;
}
/*******************************************************************************
* *
* PopupMenu *
* *
******************************************************************************/
.context-menu {
-fx-font-size: 12px;
-fx-background-color: derive(COLOR_BACKGROUND, -30%), COLOR_BACKGROUND;
-fx-background-insets: 0, 1;
-fx-padding: 1px;
-fx-effect: dropshadow(three-pass-box, rgba(0.0,0.0,0.0,0.2), 2.0, 0.0, 3.0, 3.0);
}
.context-menu > .separator {
-fx-padding: 0.0em 0.333333em 0.0em 0.333333em; /* 0 4 0 4 */
}
.menu-item {
-fx-background-color: transparent;
-fx-background-insets:0.0;
-fx-padding:0.2em 1em 0.2em 1em;
-fx-border-width: 0.0 0.0 0.0 0.0;
-fx-border-color:transparent;
}
.menu-item > .graphic-container {
-fx-padding: 0 6px 0 0;
}
.menu-item >.label {
-fx-padding: 0em 0.5em 0em 0em;
-fx-text-fill: COLOR_TEXT;
}
.menu-item:disabled > .label {
-fx-opacity: 0.6;
}
.menu-item:focused {
-fx-background-color: COLOR_BORDER_FOCUS, linear-gradient(to bottom, #DAECFC 0%, #C4E0FC 100%);
-fx-background-insets: 0, 1;
}
.menu-item:disabled {
-fx-opacity: 0.6;
}
/*******************************************************************************
* *
* Tooltip *
* *
******************************************************************************/
.tooltip {
-fx-background-color: COLOR_BACKGROUND;
-fx-padding: 0.2em 0.4em 0.2em 0.4em;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 0);
-fx-font-size: 0.9em;
}
/****************************************************************************
* *
* Separator *
* *
****************************************************************************/
.separator .line {
-fx-border-style: solid;
-fx-border-width: 1px;
-fx-background-color: null;
}
.separator:horizontal .line {
-fx-border-color: COLOR_BORDER transparent transparent transparent;
}
/*******************************************************************************
* *
* CheckBox *
* *
******************************************************************************/
.check-box {
-fx-label-padding: 0 0 0 6px;
-fx-text-fill: COLOR_TEXT;
-fx-border-style: dotted inside;
-fx-border-color: transparent;
}
.check-box > .box {
-fx-padding: 1px;
-fx-border-color: COLOR_BORDER;
-fx-background-color: #FFF;
}
.check-box > .box > .mark {
-fx-background-color: transparent;
-fx-padding: 4px;
-fx-shape: "M-1,4, L-1,5.5 L3.5,8.5 L9,0 L9,-1 L7,-1 L3,6 L1,4 Z";
}
/* armed/hover: */
.check-box:hover > .box,
.check-box:armed > .box {
-fx-border-color: COLOR_BORDER_FOCUS;
}
/* focused */
.check-box:focused {
-fx-border-color: black;
}
/* selected: */
.check-box:selected > .box > .mark {
-fx-background-color: black;
}
/* disabled: */
.check-box:disabled > .box {
-fx-background-color: #F1F1F1;
}
.check-box:disabled:selected > .box > .mark {
-fx-background-color: COLOR_TEXT_DISABLED;
}
/*******************************************************************************
* *
* ChoiceBox *
* *
******************************************************************************/
.choice-box {
-fx-background-color: COLOR_BORDER, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
-fx-background-insets: 0, 1;
-fx-background-radius: 0, 0;
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
-fx-text-fill: COLOR_TEXT;
}
.choice-box:focused {
-fx-border-style: dotted inside;
}
.choice-box > .open-button > .arrow {
-fx-background-color: transparent, COLOR_TEXT;
-fx-background-insets: 0 0 -1 0, 0;
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
.choice-box .context-menu {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
}
/****************************************************************************
* *
* ProgressIndicator *
* *
****************************************************************************/
.progress-indicator {
-fx-indeterminate-segment-count: 12;
-fx-spin-enabled: true;
}
.progress-indicator:indeterminate > .spinner {
-fx-padding: 0.083333em;
}
.progress-indicator:indeterminate .segment {
-fx-background-color: COLOR_BORDER_FOCUS;
}
.progress-indicator:indeterminate .segment0 {
-fx-shape:"M10,0C9.998,0,9.995,0,9.992,0C9.991,0,9.991,0,9.99,0C9.988,0,9.986,0,9.985,0S9.982,0,9.981,0S9.979,0,9.978,0S9.975,0,9.974,0S9.972,0,9.971,0C9.969,0,9.968,0,9.966,0H9.965c-0.007,0-0.014,0-0.02,0C9.944,0,9.944,0,9.944,0C9.941,0,9.939,0,9.937,0c-0.77,0.007-1.389,0.634-1.384,1.404C8.557,2.176,9.185,2.8,9.956,2.8c0.001,0,0.003,0,0.004,0H10c0.773-0.002,1.4-0.63,1.4-1.404c0-0.77-0.622-1.393-1.392-1.396C10.006,0,10.003,0,10,0L10,0z";
}
.progress-indicator:indeterminate .segment1 {
-fx-shape:"M5.688,1.156c-0.236,0-0.476,0.06-0.696,0.187C4.98,1.349,4.969,1.356,4.958,1.363c0,0-0.001,0-0.001,0C4.955,1.364,4.954,1.365,4.952,1.366c-0.001,0-0.002,0.001-0.004,0.002c0,0,0,0-0.001,0C4.944,1.371,4.94,1.373,4.936,1.375c0,0,0,0-0.001,0C4.933,1.377,4.931,1.378,4.929,1.38C4.267,1.772,4.046,2.624,4.438,3.288c0.261,0.444,0.73,0.692,1.212,0.692c0.24,0,0.484-0.062,0.706-0.192l0.034-0.02C7.058,3.378,7.283,2.52,6.896,1.851C6.636,1.405,6.168,1.156,5.688,1.156L5.688,1.156z";
}
.progress-indicator:indeterminate .segment2 {
-fx-shape:"M2.534,4.326c-0.482,0-0.951,0.25-1.209,0.697C1.323,5.027,1.321,5.029,1.32,5.031l0,0C1.319,5.033,1.318,5.034,1.317,5.036S1.315,5.039,1.314,5.04c0,0.001,0,0.002-0.001,0.002C1.312,5.044,1.311,5.046,1.31,5.048c0,0,0,0,0,0.001C1.309,5.051,1.308,5.053,1.307,5.055C1.302,5.063,1.297,5.071,1.292,5.079l0,0C1.291,5.082,1.29,5.084,1.288,5.087c-0.376,0.67-0.141,1.519,0.529,1.898c0.218,0.123,0.456,0.182,0.69,0.182c0.488,0,0.963-0.255,1.222-0.708l0.02-0.035c0.383-0.671,0.149-1.527-0.521-1.912C3.008,4.386,2.769,4.326,2.534,4.326L2.534,4.326z";
}
.progress-indicator:indeterminate .segment3 {
-fx-shape:"M1.396,8.648c-0.002,0-0.005,0-0.008,0C0.619,8.652-0.001,9.278,0,10.047c0,0.002,0,0.006,0,0.008l0,0c0,0.019,0,0.037,0,0.056c0,0.001,0,0.002,0,0.003s0,0.003,0,0.004c0.01,0.765,0.632,1.378,1.396,1.378c0.005,0,0.01,0,0.015,0c0.773-0.009,1.395-0.642,1.389-1.415v-0.04C2.794,9.27,2.166,8.648,1.396,8.648L1.396,8.648z";
}
.progress-indicator:indeterminate .segment4 {
-fx-shape:"M2.579,12.955c-0.242,0-0.487,0.062-0.71,0.194c-0.664,0.391-0.885,1.242-0.499,1.906c0.013,0.021,0.025,0.043,0.038,0.063c0.262,0.436,0.724,0.678,1.197,0.678c0.243,0,0.49-0.063,0.714-0.197c0.665-0.396,0.883-1.257,0.489-1.922l-0.02-0.034C3.526,13.201,3.059,12.955,2.579,12.955L2.579,12.955z";
}
.progress-indicator:indeterminate .segment5 {
-fx-shape:"M5.772,16.09c-0.489,0-0.965,0.257-1.223,0.712c-0.38,0.671-0.146,1.52,0.523,1.901c0.002,0.001,0.004,0.002,0.007,0.004h0c0.004,0.002,0.008,0.004,0.012,0.007c0,0,0,0,0.001,0c0.001,0.001,0.002,0.002,0.004,0.002c0.001,0.001,0.003,0.002,0.004,0.003c0,0,0.001,0,0.001,0.001c0.012,0.006,0.023,0.013,0.035,0.019c0.214,0.119,0.446,0.176,0.675,0.176c0.489,0,0.963-0.258,1.22-0.716c0.377-0.675,0.137-1.529-0.537-1.908l-0.035-0.02C6.242,16.149,6.006,16.09,5.772,16.09L5.772,16.09z";
}
.progress-indicator:indeterminate .segment6 {
-fx-shape:"M10.14,17.198c-0.006,0-0.013,0-0.02,0h-0.039c-0.773,0.011-1.394,0.646-1.385,1.419c0.008,0.767,0.631,1.382,1.396,1.382c0.003,0,0.006,0,0.009-0.001c0.024,0,0.051,0,0.075-0.001c0.769-0.016,1.38-0.648,1.367-1.418C11.53,17.813,10.904,17.198,10.14,17.198L10.14,17.198z";
}
.progress-indicator:indeterminate .segment7 {
-fx-shape:"M14.433,15.97c-0.245,0-0.494,0.064-0.72,0.2l-0.034,0.021c-0.663,0.397-0.88,1.258-0.483,1.922c0.261,0.439,0.725,0.683,1.2,0.683c0.24,0,0.484-0.062,0.707-0.194c0.022-0.013,0.044-0.025,0.065-0.039c0.656-0.399,0.866-1.254,0.469-1.913C15.373,16.212,14.909,15.97,14.433,15.97L14.433,15.97z";
}
.progress-indicator:indeterminate .segment8 {
-fx-shape:"M17.539,12.748c-0.493,0-0.973,0.261-1.229,0.723l-0.02,0.034c-0.376,0.676-0.133,1.53,0.542,1.907c0.216,0.121,0.45,0.178,0.681,0.178c0.487,0,0.96-0.256,1.217-0.71c0.003-0.006,0.007-0.012,0.01-0.019c0.007-0.013,0.015-0.025,0.021-0.038l0,0c0.002-0.003,0.003-0.005,0.004-0.008c0.369-0.675,0.124-1.521-0.55-1.893C18.001,12.805,17.769,12.748,17.539,12.748L17.539,12.748z";
}
.progress-indicator:indeterminate .segment9 {
-fx-shape:"M18.603,8.408c-0.011,0-0.021,0-0.031,0c-0.773,0.018-1.388,0.657-1.373,1.431l0.001,0.04c0.015,0.765,0.641,1.377,1.403,1.377c0.008,0,0.016,0,0.023,0c0.77-0.013,1.383-0.646,1.373-1.414c0-0.003,0-0.006,0-0.009l0,0c-0.001-0.019-0.001-0.037-0.001-0.055c0-0.001,0-0.001-0.001-0.002c0-0.002,0-0.004,0-0.006C19.979,9.012,19.358,8.408,18.603,8.408L18.603,8.408z";
}
.progress-indicator:indeterminate .segment10 {
-fx-shape:"M17.345,4.121c-0.248,0-0.5,0.066-0.728,0.205c-0.659,0.403-0.869,1.266-0.468,1.927l0.021,0.034c0.265,0.435,0.728,0.675,1.202,0.675c0.247,0,0.497-0.065,0.724-0.202c0.659-0.397,0.871-1.252,0.477-1.912c-0.007-0.011-0.013-0.021-0.02-0.032c-0.001-0.002-0.002-0.003-0.002-0.004c-0.001,0-0.001-0.001-0.001-0.002c-0.004-0.005-0.008-0.011-0.011-0.017c0-0.001,0-0.001-0.001-0.001c-0.001-0.002-0.002-0.004-0.004-0.007C18.271,4.358,17.813,4.121,17.345,4.121L17.345,4.121z";
}
.progress-indicator:indeterminate .segment11 {
-fx-shape:"M14.104,1.039c-0.494,0-0.974,0.264-1.227,0.729c-0.37,0.679-0.12,1.531,0.559,1.903l0.034,0.019c0.214,0.117,0.445,0.173,0.673,0.173c0.495,0,0.976-0.262,1.231-0.726c0.371-0.674,0.129-1.519-0.542-1.894c-0.012-0.006-0.024-0.013-0.036-0.02c-0.007-0.004-0.014-0.008-0.021-0.012c-0.003-0.001-0.006-0.003-0.009-0.005C14.556,1.094,14.329,1.039,14.104,1.039L14.104,1.039z";
}
/*****************************************************************************
* *
* I/O Chart *
* *
*****************************************************************************/
.chart {
-fx-padding: 5px;
}
/* legend */
.chart-legend {
-fx-text-fill: COLOR_TEXT;
-fx-background-color: transparent;
-fx-padding: 0.4em;
}
.chart-line-symbol {
-fx-background-radius: 5px;
-fx-padding: 5px;
}
.default-color0.chart-line-symbol { -fx-background-color: COLOR_CHART_GREEN; }
.default-color1.chart-line-symbol { -fx-background-color: COLOR_CHART_RED; }
/* axis */
.axis {
-fx-tick-label-font-size: 0.8em;
-fx-tick-label-fill: COLOR_TEXT;
}
.axis-label {
-fx-text-fill: COLOR_TEXT;
-fx-padding: 0 0 0.8em 0;
}
.axis:left {
-fx-border-color: transparent COLOR_BORDER transparent transparent;
}
.axis-tick-mark,
.axis-minor-tick-mark {
-fx-fill: null;
-fx-stroke: #CCC;
}
/* content */
.chart-content {
-fx-padding: 10px;
}
.chart-horizontal-grid-lines {
-fx-stroke: COLOR_BORDER;
}
.chart-alternative-column-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-alternative-row-fill {
-fx-fill: null;
-fx-stroke: null;
}
.chart-vertical-zero-line,
.chart-horizontal-zero-line {
-fx-stroke: COLOR_BORDER;
}
.chart-series-line {
-fx-stroke-width: 2px;
}
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
/*******************************************************************************
* *
* Dialog *
* *
******************************************************************************/
.dialog-pane {
-fx-background-color: COLOR_BACKGROUND;
-fx-padding: 20px 20px 20px 96px;
-fx-background-image: url("/img/dialog-appicon.png");
-fx-background-repeat: no-repeat;
-fx-background-position: 20px 20px;
}
/* HEADER */
.dialog-pane:header .header-panel {
-fx-padding: 0 0 12px 0;
}
/* TITLE */
.dialog-pane:header .header-panel .label {
-fx-font-weight: bold;
-fx-wrap-text: true;
}
/* CONTENT LABEL */
.dialog-pane > .content {
-fx-alignment: top-left;
-fx-wrap-text: true;
-fx-font-size: 11px;
-fx-line-spacing: 1.0;
}
/* BUTTONS */
.dialog-pane > .button-bar > .container {
-fx-padding: 20px 0 0 0;
}
.dialog-pane > .button-bar .button:default {
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_BACKGROUND;
}
.dialog-pane > .button-bar .button:default:armed {
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
}
.alert.confirmation.dialog-pane,
.text-input-dialog.dialog-pane,
.choice-dialog.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-confirm.png");
}
.alert.information.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-information.png");
}
.alert.error.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-error.png");
}
.alert.warning.dialog-pane {
-fx-padding: 20px 20px 20px 80px;
-fx-background-image: url("/img/dialog-warning.png");
}

View File

@@ -1,79 +0,0 @@
<?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
Jean-Noël Charon - password strength meter
-->
<?import java.net.URL?>
<?import java.lang.String?>
<?import org.cryptomator.ui.controls.SecPasswordField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.text.TextFlow?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<GridPane fx:controller="org.cryptomator.ui.controllers.ChangePasswordController" fx:id="root" 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="12.0" bottom="24.0" left="12.0" />
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="38.2"/>
<ColumnConstraints percentWidth="61.8"/>
</columnConstraints>
<children>
<!-- Row 0 -->
<Label text="%changePassword.label.oldPassword" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="oldPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 1 -->
<Label text="%changePassword.label.newPassword" GridPane.rowIndex="1" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="newPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 2 -->
<Label text="%changePassword.label.retypePassword" GridPane.rowIndex="2" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3 -->
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
<HBox spacing="6.0" prefHeight="6.0" cacheShape="true" cache="true">
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel0" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel1" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel2" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel3" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel4" cacheShape="true" cache="true" />
</HBox>
<Label fx:id="passwordStrengthLabel" styleClass="caption-label" cache="true" cacheShape="true" text="" />
</VBox>
<!-- Row 4 -->
<Label text="%initialize.label.doNotForget" wrapText="true" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="4" />
<!-- Row 5 -->
<Button fx:id="changePasswordButton" text="%changePassword.button.change" defaultButton="true" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickChangePasswordButton" disable="true" cacheShape="true" cache="true"/>
<!-- Row 6 -->
<TextFlow GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
<children>
<Text fx:id="messageText" cache="true" />
<Hyperlink fx:id="downloadsPageLink" text="%changePassword.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
</children>
</TextFlow>
</children>
</GridPane>

View File

@@ -1,67 +0,0 @@
<?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
Jean-Noël Charon - password strength meter
-->
<?import java.lang.*?>
<?import java.net.URL?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ProgressIndicator?>
<?import org.cryptomator.ui.controls.SecurePasswordField?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<GridPane fx:controller="org.cryptomator.ui.controllers.InitializeController" fx:id="root" 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="12.0" bottom="24.0" left="12.0" />
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="38.2"/>
<ColumnConstraints percentWidth="61.8"/>
</columnConstraints>
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
<SecurePasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
<SecurePasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<!-- Row 2 -->
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
<HBox spacing="6.0" prefHeight="6.0" cacheShape="true" cache="true">
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel0" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel1" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel2" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel3" cacheShape="true" cache="true" />
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel4" cacheShape="true" cache="true" />
</HBox>
<Label fx:id="passwordStrengthLabel" styleClass="caption-label" cache="true" cacheShape="true" text="" />
</VBox>
<!-- Row 3 -->
<Label text="%initialize.label.doNotForget" wrapText="true" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" />
<!-- Row 4 -->
<Button fx:id="okButton" cache="true" cacheShape="true" defaultButton="true" disable="true" focusTraversable="false" onAction="#initializeVault" prefWidth="150.0" text="%initialize.button.ok" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" GridPane.rowIndex="4" />
<!-- Row 5 -->
<Label fx:id="messageLabel" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="5" />
</children>
</GridPane>

View File

@@ -1,78 +0,0 @@
<?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.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Arc?>
<HBox fx:controller="org.cryptomator.ui.controllers.MainController" fx:id="root" prefHeight="440.0" prefWidth="652.0" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<padding><Insets top="20" right="20" bottom="20" left="20.0"/></padding>
<fx:define>
<ContextMenu fx:id="vaultListCellContextMenu">
<items>
<MenuItem text="%main.directoryList.contextMenu.remove" onAction="#didClickRemoveSelectedEntry">
<graphic><Label text="&#xf12a;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem text="%main.directoryList.contextMenu.changePassword" fx:id="changePasswordMenuItem" onAction="#didClickChangePassword">
<graphic><Label text="&#xf2bf;" styleClass="ionicons"/></graphic>
</MenuItem>
</items>
</ContextMenu>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem text="%main.addDirectory.contextMenu.new" onAction="#didClickCreateNewVault">
<graphic><Label text="&#xf2c7;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem text="%main.addDirectory.contextMenu.open" onAction="#didClickAddExistingVaults">
<graphic><Label text="&#xf2f5;" styleClass="ionicons"/></graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
<VBox prefWidth="200.0" cacheShape="true" cache="true">
<StackPane VBox.vgrow="ALWAYS" cacheShape="true" cache="true">
<ListView fx:id="vaultList" focusTraversable="true" cacheShape="true" cache="true"/>
<AnchorPane fx:id="emptyListInstructions" cacheShape="true" cache="true">
<HBox AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.bottomAnchor="100.0" alignment="CENTER" cacheShape="true" cache="true">
<Label textAlignment="CENTER" text="%main.emptyListInstructions" wrapText="true" cacheShape="true" cache="true"/>
</HBox>
<Arc AnchorPane.bottomAnchor="5.0" type="OPEN" centerX="-10.0" centerY="0.0" radiusY="100.0" radiusX="60.0" startAngle="0" length="-60.0" strokeWidth="1" stroke="BLACK" fill="TRANSPARENT"/>
</AnchorPane>
</StackPane>
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar" cacheShape="true" cache="true">
<items>
<ToggleButton text="&#xf489;" styleClass="ionicons" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="true" cacheShape="true" cache="true"/>
<Button text="&#xf462;" styleClass="ionicons" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="true" cacheShape="true" cache="true"/>
<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
<!-- future use: -->
<ToggleButton text="&#xf43c;" styleClass="ionicons" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="true" cacheShape="true" cache="true"/>
</items>
</ToolBar>
</VBox>
<Pane fx:id="contentPane" prefWidth="400.0" cacheShape="true" cache="true"/>
</HBox>

View File

@@ -1,20 +0,0 @@
<?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.lang.String?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox fx:controller="org.cryptomator.ui.controllers.NotFoundController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label text="%notfound.label" textAlignment="CENTER" wrapText="true" cacheShape="true" cache="true"/>
</VBox>

View File

@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 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
-->
<?import javafx.scene.layout.GridPane?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<VBox fx:controller="org.cryptomator.ui.controllers.SettingsController" fx:id="root" prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
<GridPane VBox.vgrow="ALWAYS" vgap="12.0" hgap="12.0" cacheShape="true" cache="true">
<padding>
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="45.00"/>
<ColumnConstraints percentWidth="55.00"/>
</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" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
<!-- Row 2 -->
<Label fx:id="volumeLabel" GridPane.rowIndex="2" GridPane.columnIndex="0" text="%settings.volume.label" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="volume" cacheShape="true" cache="true" />
<!-- Row 3 Alt 1-->
<Label fx:id="portFieldLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.webdav.port.label" cacheShape="true" cache="true" />
<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
<TextField fx:id="portField" cacheShape="true" cache="true" promptText="%settings.webdav.port.prompt" />
<Button text="%settings.webdav.port.apply" fx:id="changePortButton" onAction="#changePort"/>
</HBox>
<!-- Row 4 -->
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.webdav.prefGvfsScheme.label" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
</children>
</GridPane>
</VBox>

View File

@@ -1,103 +0,0 @@
<?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 javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<?import org.cryptomator.ui.controls.SecurePasswordField?>
<VBox fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" spacing="12" alignment="BOTTOM_CENTER" xmlns:fx="http://javafx.com/fxml" prefWidth="400">
<padding>
<Insets top="24" right="12" bottom="24" left="12" />
</padding>
<!-- Password Field -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<Label text="%unlock.label.password" HBox.hgrow="NEVER"/>
<SecurePasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" maxWidth="Infinity" HBox.hgrow="ALWAYS"/>
</HBox>
<!-- Unlock Button / Advanced Options Button -->
<HBox spacing="12" alignment="CENTER_RIGHT">
<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton"/>
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true" contentDisplay="TEXT_ONLY">
<graphic>
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12"/>
</graphic>
</Button>
</HBox>
<!-- Status Text -->
<TextFlow prefWidth="400" textAlignment="LEFT">
<children>
<Text fx:id="messageText"/>
<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" managed="false" onAction="#didClickDownloadsLink"/>
</children>
</TextFlow>
<!-- Advanced Options -->
<VBox fx:id="advancedOptions" spacing="12" VBox.vgrow="ALWAYS" visible="false">
<Separator/>
<!-- Mount Name -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<Label text="%unlock.label.mountName"/>
<TextField fx:id="mountName" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
</HBox>
<!-- Save Password -->
<CheckBox fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox"/>
<!-- Auto Unlock -->
<CheckBox fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup"/>
<!-- Reveal Drive -->
<CheckBox fx:id="revealAfterMount" text="%unlock.label.revealAfterMount"/>
<!-- Read-Only -->
<CheckBox fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode"/>
<!-- Custom Mount Point -->
<CheckBox fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath" onAction="#didClickCustomMountPointCheckbox"/>
<HBox fx:id="customMountPoint" spacing="6" alignment="BASELINE_LEFT" >
<padding>
<Insets left="20.0"/>
<Insets bottom="-19.0"/>
</padding>
<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS"/>
<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="&#xf434;" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true"/>
</HBox>
<!-- Windows Drive Letter -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<CheckBox fx:id="useCustomWinDriveLetter" text="%unlock.label.winDriveLetter" onAction="#didClickCustomWinDriveLetterCheckbox"/>
<ChoiceBox fx:id="winDriveLetter" HBox.hgrow="NEVER" maxWidth="Infinity"/>
</HBox>
<!-- Mount Flags -->
<HBox spacing="12" alignment="BASELINE_LEFT">
<CheckBox fx:id="useCustomMountFlags" text="%unlock.label.useCustomMountFlags"/>
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
</HBox>
</VBox>
</VBox>

View File

@@ -1,49 +0,0 @@
<?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 javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox fx:controller="org.cryptomator.ui.controllers.UnlockedController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="6.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<fx:define>
<ContextMenu fx:id="moreOptionsMenu">
<items>
<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
<LineChart fx:id="ioGraph" VBox.vgrow="ALWAYS" animated="false" createSymbols="false" prefHeight="340.0" legendVisible="true" legendSide="BOTTOM" verticalZeroLineVisible="false" verticalGridLinesVisible="false" horizontalGridLinesVisible="true" cacheShape="true" cache="true" cacheHint="SPEED">
<xAxis><NumberAxis fx:id="xAxis" forceZeroInRange="false" tickMarkVisible="false" minorTickVisible="false" tickLabelsVisible="false" autoRanging="false" cacheShape="true" cache="true" /></xAxis>
<yAxis><NumberAxis label="%unlocked.ioGraph.yAxis.label" autoRanging="true" forceZeroInRange="true" cacheShape="true" cache="true" /></yAxis>
</LineChart>
<HBox VBox.vgrow="NEVER">
<Pane HBox.hgrow="ALWAYS">
<Label fx:id="messageLabel" cacheShape="true" cache="true" />
</Pane>
<HBox styleClass="segmented-button-bar" HBox.hgrow="NEVER" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
<Button text="%unlocked.button.lock" styleClass="first" onAction="#didClickLockVault" focusTraversable="true" cacheShape="true" cache="true"/>
<ToggleButton text="&#xf104;" styleClass="last,ionicons" focusTraversable="true" fx:id="moreOptionsButton" onAction="#didClickMoreOptions" />
</HBox>
</HBox>
</VBox>

View File

@@ -1,54 +0,0 @@
<?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.lang.String?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.Button?>
<?import org.cryptomator.ui.controls.SecurePasswordField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.CheckBox?>
<GridPane fx:controller="org.cryptomator.ui.controllers.UpgradeController" fx:id="root" 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="12.0" bottom="24.0" left="12.0" />
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="38.2" />
<ColumnConstraints percentWidth="61.8" />
</columnConstraints>
<children>
<Label fx:id="upgradeTitleLabel" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
<Label fx:id="upgradeMsgLabel" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
<Pane prefHeight="12.0" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
</Pane>
<Label text="%unlock.label.password" GridPane.rowIndex="3" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecurePasswordField fx:id="passwordField" GridPane.rowIndex="3" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<CheckBox fx:id="confirmationCheckbox" text="%upgrade.confirmation.label" wrapText="true" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
<HBox alignment="CENTER_RIGHT" GridPane.hgrow="ALWAYS" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
</HBox>
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" cacheHint="SPEED" />
<Label fx:id="errorLabel" wrapText="true" GridPane.rowIndex="7" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
</children>
</GridPane>

View File

@@ -31,5 +31,7 @@
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
</children>
</HBox>
<!-- TODO windows drive letter, see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L283-L298 -->
</children>
</VBox>

View File

@@ -1,34 +0,0 @@
<?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 javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ProgressIndicator?>
<VBox fx:controller="org.cryptomator.ui.controllers.WelcomeController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true" prefHeight="64.0">
<HBox alignment="CENTER" spacing="5.0" 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 wrapText="true" textAlignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" disable="true" />
</VBox>
<ImageView fitHeight="200.0" preserveRatio="true" smooth="true" cache="true" style="-fx-background-color: green;">
<Image url="/bot_welcome.png"/>
</ImageView>
<VBox prefHeight="64.0"/>
</VBox>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,23 +1,21 @@
package org.cryptomator.ui.util;
package org.cryptomator.ui.common;
import org.cryptomator.ui.l10n.Localization;
import com.google.common.base.Strings;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.time.Duration;
import java.util.ResourceBundle;
public class PasswordStrengthUtilTest {
@Test
public void testLongPasswords() {
PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(Localization.class));
StringBuilder longPwBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
longPwBuilder.append('x');
}
PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class));
String longPw = Strings.repeat("x", 10_000);
Assertions.assertTimeout(Duration.ofSeconds(5), () -> {
util.computeRate(longPwBuilder.toString());
util.computeRate(longPw);
});
}

View File

@@ -1,27 +0,0 @@
package org.cryptomator.ui.controls;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
public class Foo {
@Test
public void foo() {
StringProperty str = new SimpleStringProperty("foo");
CountDownLatch done = new CountDownLatch(1);
str.addListener(observable -> {
Assertions.assertEquals("bar", str.get());
done.countDown();
});
str.set("bar");
Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
done.await();
});
}
}

View File

@@ -1,161 +0,0 @@
package org.cryptomator.ui.model.upgrade;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.l10n.LocalizationMock;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
public class UpgradeVersion3to4Test {
private static final Localization L10N = new LocalizationMock();
private static final String NULL_KEY_CONTENTS = "{" //
+ " \"version\": 3," //
+ " \"scryptSalt\": \"AAAAAAAAAAA=\"," //
+ " \"scryptCostParam\": 16384," //
+ " \"scryptBlockSize\": 8," //
+ " \"primaryMasterKey\": \"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
+ " \"hmacMasterKey\": \"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
+ " \"versionMac\": \"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"" //
+ "}";
private final UpgradeStrategy upgradeStrategy = new UpgradeVersion3to4(L10N);
private FileSystem fs;
private Path fsRoot;
private Vault vault;
private Path dataDir;
private Path metadataDir;
@BeforeEach
public void setup() throws IOException {
fs = Jimfs.newFileSystem(Configuration.unix());
fsRoot = fs.getPath("/");
dataDir = fsRoot.resolve("d");
metadataDir = fsRoot.resolve("m");
vault = Mockito.mock(Vault.class);
Mockito.when(vault.getPath()).thenReturn(fsRoot);
Files.write(fsRoot.resolve("masterkey.cryptomator"), NULL_KEY_CONTENTS.getBytes(StandardCharsets.US_ASCII));
}
@AfterEach
public void teardown() throws IOException {
fs.close();
}
@Test
public void upgradeFailsWithWrongPassword() throws UpgradeFailedException {
UpgradeFailedException e = Assertions.assertThrows(UpgradeFailedException.class, () -> {
upgradeStrategy.upgrade(vault, "asdd");
});
Assertions.assertEquals("unlock.errorMessage.wrongPassword", e.getMessage());
}
@Test
public void upgradeCreatesBackup() throws UpgradeFailedException {
upgradeStrategy.upgrade(vault, "asd");
Assertions.assertTrue(Files.exists(fsRoot.resolve("masterkey.cryptomator.bkup")));
}
@Test
public void upgradeRenamesSimpleDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH_");
Files.createFile(oldFile);
upgradeStrategy.upgrade(vault, "asd");
Path newFile = lvl2Dir.resolve("0ABCDEFGH");
Assertions.assertTrue(Files.exists(newFile));
Assertions.assertTrue(Files.notExists(oldFile));
}
@Test
public void upgradeRenamesConflictingDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH_ (1)");
Files.createFile(oldFile);
upgradeStrategy.upgrade(vault, "asd");
Path newFile = lvl2Dir.resolve("0ABCDEFGH (1)");
Assertions.assertTrue(Files.exists(newFile));
Assertions.assertTrue(Files.notExists(oldFile));
}
@Test
public void upgradeDontRenameNonDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH");
Files.createFile(oldFile);
upgradeStrategy.upgrade(vault, "asd");
Assertions.assertTrue(Files.exists(oldFile));
}
@Test
public void upgradeRenameSimpleLongDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH.lng");
Files.createFile(oldFile);
Path oldMetadataFile = metadataDir.resolve("AB/CD/ABCDEFGH.lng");
Files.createDirectories(oldMetadataFile.getParent());
Files.write(oldMetadataFile, "OPQRSTUVWXYZ====_".getBytes(StandardCharsets.UTF_8));
upgradeStrategy.upgrade(vault, "asd");
// hex2base32(sha1("0OPQRSTUVWXYZ====")) = DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G
Path newMetadataFile = metadataDir.resolve("DD/LC/DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G.lng");
Path newFile = lvl2Dir.resolve("DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G.lng");
Assertions.assertTrue(Files.exists(newFile));
Assertions.assertTrue(Files.exists(newMetadataFile));
Assertions.assertTrue(Files.notExists(oldFile));
}
@Test
public void upgradeRenameConflictingLongDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH (1).lng");
Files.createFile(oldFile);
Path oldMetadataFile = metadataDir.resolve("AB/CD/ABCDEFGH.lng");
Files.createDirectories(oldMetadataFile.getParent());
Files.write(oldMetadataFile, "OPQRSTUVWXYZ====_".getBytes(StandardCharsets.UTF_8));
upgradeStrategy.upgrade(vault, "asd");
// hex2base32(sha1("0OPQRSTUVWXYZ====")) = DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G
Path newMetadataFile = metadataDir.resolve("DD/LC/DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G.lng");
Path newFile = lvl2Dir.resolve("DDLCFQ3ODTEAHEZJPHIJQRDHROB3K42G (1).lng");
Assertions.assertTrue(Files.exists(newFile));
Assertions.assertTrue(Files.exists(newMetadataFile));
Assertions.assertTrue(Files.notExists(oldFile));
}
@Test
public void upgradeDontRenameLongNonDirFile() throws IOException, UpgradeFailedException {
Path lvl2Dir = dataDir.resolve("AB/CDEFGHIJKLMNOPQRSTUVWXYZ234567");
Files.createDirectories(lvl2Dir);
Path oldFile = lvl2Dir.resolve("ABCDEFGH.lng");
Files.createFile(oldFile);
Path oldMetadataFile = metadataDir.resolve("AB/CD/ABCDEFGH.lng");
Files.createDirectories(oldMetadataFile.getParent());
Files.write(oldMetadataFile, "OPQRSTUVWXYZ====".getBytes(StandardCharsets.UTF_8));
upgradeStrategy.upgrade(vault, "asd");
Assertions.assertTrue(Files.exists(oldFile));
}
}