Merge branch 'master' into patches-1.0.x

# Conflicts:
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-api/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
This commit is contained in:
Sebastian Stenzel
2016-03-25 16:42:44 +01:00
25 changed files with 512 additions and 181 deletions

View File

@@ -27,7 +27,12 @@ public class CryptoEngineModule {
@Provides
public SecureRandom provideSecureRandom() {
try {
return SecureRandom.getInstanceStrong();
// https://tersesystems.com/2015/12/17/the-right-way-to-use-securerandom/
final SecureRandom nativeRandom = SecureRandom.getInstanceStrong();
byte[] seed = nativeRandom.generateSeed(55); // NIST SP800-90A suggests 440 bits for SHA1 seed
SecureRandom sha1Random = SecureRandom.getInstance("SHA1PRNG");
sha1Random.setSeed(seed);
return sha1Random;
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No strong PRNGs available.", e);
}

View File

@@ -29,5 +29,5 @@ interface CryptomatorComponent {
Localization localization();
TrayIconUtil trayIconUtil();
ExitUtil exitUtil();
}

View File

@@ -38,22 +38,41 @@ import javafx.application.Platform;
import javafx.stage.Stage;
@Singleton
class TrayIconUtil {
class ExitUtil {
private static final Logger LOG = LoggerFactory.getLogger(TrayIconUtil.class);
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
private final Stage mainWindow;
private final Localization localization;
private final Settings settings;
@Inject
public TrayIconUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings) {
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings) {
this.mainWindow = mainWindow;
this.localization = localization;
this.settings = settings;
}
public void initTrayIcon(Runnable exitCommand) {
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) {
final TrayIcon trayIcon = createTrayIcon(exitCommand);
try {
SystemTray.getSystemTray().add(trayIcon);

View File

@@ -67,7 +67,7 @@ public class MainApplication extends Application {
// show window and start observing its focus:
primaryStage.show();
ActiveWindowStyleSupport.startObservingFocus(primaryStage);
comp.trayIconUtil().initTrayIcon(this::quit);
comp.exitUtil().initExitHandler(this::quit);
// open files, if requested during startup:
for (String arg : getParameters().getUnnamed()) {

View File

@@ -12,7 +12,6 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -37,19 +36,18 @@ import javafx.scene.control.Hyperlink;
import javafx.scene.text.Text;
@Singleton
public class ChangePasswordController extends AbstractFXMLViewController {
public class ChangePasswordController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private final Application app;
private final Localization localization;
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private Optional<ChangePasswordListener> listener = Optional.empty();
@Inject
public ChangePasswordController(Application app, Localization localization) {
super(localization);
this.app = app;
this.localization = localization;
}
@FXML
@@ -83,11 +81,6 @@ public class ChangePasswordController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/change_password.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
// ****************************************
// Downloads link
// ****************************************
@@ -143,13 +136,13 @@ public class ChangePasswordController extends AbstractFXMLViewController {
private void invokeListenerLater(ChangePasswordListener listener) {
Platform.runLater(() -> {
listener.didChangePassword(this);
listener.didChangePassword();
});
}
@FunctionalInterface
interface ChangePasswordListener {
void didChangePassword(ChangePasswordController ctrl);
void didChangePassword();
}
}

View File

@@ -13,7 +13,6 @@ import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -34,17 +33,16 @@ import javafx.scene.control.Button;
import javafx.scene.control.Label;
@Singleton
public class InitializeController extends AbstractFXMLViewController {
public class InitializeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
private final Localization localization;
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private Optional<InitializationListener> listener = Optional.empty();
@Inject
public InitializeController(Localization localization) {
this.localization = localization;
super(localization);
}
@FXML
@@ -71,11 +69,6 @@ public class InitializeController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/initialize.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
// ****************************************
// OK button
// ****************************************
@@ -111,13 +104,13 @@ public class InitializeController extends AbstractFXMLViewController {
private void invokeListenerLater(InitializationListener listener) {
Platform.runLater(() -> {
listener.didInitialize(this);
listener.didInitialize();
});
}
@FunctionalInterface
interface InitializationListener {
void didInitialize(InitializeController ctrl);
void didInitialize();
}
}

View File

@@ -0,0 +1,18 @@
package org.cryptomator.ui.controllers;
import org.cryptomator.ui.settings.Localization;
abstract class LocalizedFXMLViewController extends AbstractFXMLViewController {
protected final Localization localization;
public LocalizedFXMLViewController(Localization localization) {
this.localization = localization;
}
@Override
protected Localization getFxmlResourceBundle() {
return localization;
}
}

View File

@@ -9,7 +9,6 @@
package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -39,10 +38,9 @@ import javafx.scene.control.cell.CheckBoxListCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class MacWarningsController extends AbstractFXMLViewController {
public class MacWarningsController extends LocalizedFXMLViewController {
private final Application application;
private final Localization localization;
private final ObservableList<Warning> warnings = FXCollections.observableArrayList();
private final ListChangeListener<String> unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange;
private final ChangeListener<Boolean> stageVisibilityChangeListener = this::windowVisibilityDidChange;
@@ -51,8 +49,8 @@ public class MacWarningsController extends AbstractFXMLViewController {
@Inject
public MacWarningsController(Application application, Localization localization) {
super(localization);
this.application = application;
this.localization = localization;
}
@FXML
@@ -85,11 +83,6 @@ public class MacWarningsController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/mac_warnings.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
@Override
public void initStage(Stage stage) {
super.initStage(stage);
@@ -130,7 +123,7 @@ public class MacWarningsController extends AbstractFXMLViewController {
private void windowVisibilityDidChange(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (Boolean.TRUE.equals(newValue)) {
stage.setTitle(String.format(localization.getString("macWarnings.windowTitle"), vault.get().getName()));
stage.setTitle(String.format(localization.getString("macWarnings.windowTitle"), vault.get().name().getValue()));
warnings.addAll(vault.get().getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
vault.get().getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
} else {

View File

@@ -16,7 +16,6 @@ import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Named;
@@ -57,15 +56,16 @@ import javafx.stage.FileChooser;
import javafx.stage.Stage;
@Singleton
public class MainController extends AbstractFXMLViewController {
public class MainController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
private final Stage mainWindow;
private final Localization localization;
private final VaultFactory vaultFactoy;
private final Lazy<WelcomeController> welcomeController;
private final Lazy<InitializeController> initializeController;
private final Lazy<NotFoundController> notFoundController;
private final Lazy<UpgradeController> upgradeController;
private final Lazy<UnlockController> unlockController;
private final Provider<UnlockedController> unlockedControllerProvider;
private final Lazy<ChangePasswordController> changePasswordController;
@@ -73,20 +73,22 @@ public class MainController extends AbstractFXMLViewController {
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
private final MonadicBinding<Boolean> isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);
private final MonadicBinding<Boolean> isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);;
private final Binding<Boolean> canEditSelectedVault = EasyBind.combine(selectedVault.isNull(), isSelectedVaultUnlocked.orElse(false), Boolean::logicalOr);
private final BooleanBinding isShowingSettings;
private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
Lazy<InitializeController> initializeController, Lazy<UnlockController> unlockController, Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController,
Lazy<SettingsController> settingsController) {
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController) {
super(localization);
this.mainWindow = mainWindow;
this.localization = localization;
this.vaultFactoy = vaultFactoy;
this.welcomeController = welcomeController;
this.initializeController = initializeController;
this.notFoundController = notFoundController;
this.upgradeController = upgradeController;
this.unlockController = unlockController;
this.unlockedControllerProvider = unlockedControllerProvider;
this.changePasswordController = changePasswordController;
@@ -133,9 +135,8 @@ public class MainController extends AbstractFXMLViewController {
removeVaultButton.disableProperty().bind(canEditSelectedVault);
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
EasyBind.subscribe(activeController, this::activeControllerDidChange);
EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
EasyBind.subscribe(isSelectedVaultUnlocked, this::selectedVaultUnlockedDidChange);
EasyBind.subscribe(activeController, this::activeControllerDidChange);
EasyBind.subscribe(isShowingSettings, settingsButton::setSelected);
EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected);
}
@@ -145,11 +146,6 @@ public class MainController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/main.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
final DirectoryListCell cell = new DirectoryListCell();
cell.setVaultContextMenu(vaultListCellContextMenu);
@@ -172,19 +168,12 @@ public class MainController extends AbstractFXMLViewController {
@FXML
private void didClickCreateNewVault(ActionEvent event) {
final FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator vault", "*" + Vault.VAULT_FILE_EXTENSION));
final File file = fileChooser.showSaveDialog(mainWindow);
if (file == null) {
return;
}
try {
final Path vaultDir;
// enforce .cryptomator file extension:
if (!file.getName().endsWith(Vault.VAULT_FILE_EXTENSION)) {
vaultDir = file.toPath().resolveSibling(file.getName() + Vault.VAULT_FILE_EXTENSION);
} else {
vaultDir = file.toPath();
}
final Path vaultDir = file.toPath();
if (!Files.exists(vaultDir)) {
Files.createDirectory(vaultDir);
}
@@ -272,6 +261,10 @@ public class MainController extends AbstractFXMLViewController {
}
if (newValue.isUnlocked()) {
this.showUnlockedView(newValue);
} else if (!newValue.doesVaultDirectoryExist()) {
this.showNotFoundView();
} else if (newValue.isValidVaultDirectory() && newValue.needsUpgrade()) {
this.showUpgradeView();
} else if (newValue.isValidVaultDirectory()) {
this.showUnlockView();
} else {
@@ -279,29 +272,23 @@ public class MainController extends AbstractFXMLViewController {
}
}
private void selectedVaultUnlockedDidChange(Boolean unlocked) {
if (unlocked == null) {
// no vault selected -> no-op
} else if (unlocked) {
Platform.setImplicitExit(false);
this.showUnlockedView(selectedVault.get());
} else {
this.showUnlockView();
}
}
// ****************************************
// Public Bindings
// ****************************************
public Binding<String> windowTitle() {
return EasyBind.monadic(selectedVault).map(Vault::getName).orElse(localization.getString("app.name"));
return EasyBind.monadic(selectedVault).flatMap(Vault::name).orElse(localization.getString("app.name"));
}
// ****************************************
// Subcontroller for right panel
// ****************************************
private void showNotFoundView() {
final NotFoundController ctrl = notFoundController.get();
activeController.set(ctrl);
}
private void showInitializeView() {
final InitializeController ctrl = initializeController.get();
ctrl.vault.bind(selectedVault);
@@ -309,16 +296,35 @@ public class MainController extends AbstractFXMLViewController {
activeController.set(ctrl);
}
public void didInitialize(InitializeController ctrl) {
public void didInitialize() {
showUnlockView();
}
private void showUpgradeView() {
final UpgradeController ctrl = upgradeController.get();
ctrl.vault.bind(selectedVault);
ctrl.setListener(this::didUpgrade);
activeController.set(ctrl);
}
public void didUpgrade() {
showUnlockView();
}
private void showUnlockView() {
final UnlockController ctrl = unlockController.get();
ctrl.vault.bind(selectedVault);
ctrl.setListener(this::didUnlock);
activeController.set(ctrl);
}
public void didUnlock(Vault vault) {
Platform.setImplicitExit(false);
if (vault.equals(selectedVault.getValue())) {
this.showUnlockedView(vault);
}
}
private void showUnlockedView(Vault vault) {
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
return unlockedControllerProvider.get();
@@ -343,7 +349,7 @@ public class MainController extends AbstractFXMLViewController {
activeController.set(ctrl);
}
public void didChangePassword(ChangePasswordController ctrl) {
public void didChangePassword() {
showUnlockView();
}

View File

@@ -0,0 +1,23 @@
package org.cryptomator.ui.controllers;
import java.net.URL;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.ui.settings.Localization;
@Singleton
public class NotFoundController extends LocalizedFXMLViewController {
@Inject
public NotFoundController(Localization localization) {
super(localization);
}
@Override
protected URL getFxmlResourceUrl() {
return getClass().getResource("/fxml/notfound.fxml");
}
}

View File

@@ -10,7 +10,6 @@ package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -28,14 +27,13 @@ import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
@Singleton
public class SettingsController extends AbstractFXMLViewController {
public class SettingsController extends LocalizedFXMLViewController {
private final Localization localization;
private final Settings settings;
@Inject
public SettingsController(Localization localization, Settings settings) {
this.localization = localization;
super(localization);
this.settings = settings;
}
@@ -44,7 +42,7 @@ public class SettingsController extends AbstractFXMLViewController {
@FXML
private TextField portField;
@FXML
private CheckBox useIpv6Checkbox;
@@ -71,11 +69,6 @@ public class SettingsController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/settings.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
private Optional<String> applicationVersion() {
return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
}

View File

@@ -10,7 +10,7 @@ package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Comparator;
import java.util.ResourceBundle;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
@@ -51,23 +51,23 @@ import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
public class UnlockController extends AbstractFXMLViewController {
public class UnlockController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
private final Application app;
private final Localization localization;
private final ExecutorService exec;
private final Lazy<FrontendFactory> frontendFactory;
private final Settings settings;
private final WindowsDriveLetters driveLetters;
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private Optional<UnlockListener> listener = Optional.empty();
@Inject
public UnlockController(Application app, Localization localization, ExecutorService exec, Lazy<FrontendFactory> frontendFactory, Settings settings, WindowsDriveLetters driveLetters) {
super(localization);
this.app = app;
this.localization = localization;
this.exec = exec;
this.frontendFactory = frontendFactory;
this.settings = settings;
@@ -127,11 +127,6 @@ public class UnlockController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/unlock.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
private void vaultChanged(Vault newVault) {
if (newVault == null) {
return;
@@ -276,21 +271,24 @@ public class UnlockController extends AbstractFXMLViewController {
progressIndicator.setVisible(true);
downloadsPageLink.setVisible(false);
CharSequence password = passwordField.getCharacters();
exec.submit(() -> this.unlock(password));
exec.submit(() -> this.unlock(vault.get(), password));
}
private void unlock(CharSequence password) {
private void unlock(Vault vault, CharSequence password) {
try {
vault.get().activateFrontend(frontendFactory.get(), settings, password);
vault.get().reveal();
vault.activateFrontend(frontendFactory.get(), settings, password);
vault.reveal();
Platform.runLater(() -> {
messageText.setText(null);
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
});
} catch (InvalidPassphraseException e) {
Platform.runLater(() -> {
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
passwordField.requestFocus();
});
} catch (UnsupportedVaultFormatException e) {
LOG.warn("Unable to unlock vault: " + e.getMessage());
Platform.runLater(() -> {
downloadsPageLink.setVisible(true);
if (e.isVaultOlderThanSoftware()) {
@@ -314,4 +312,15 @@ public class UnlockController extends AbstractFXMLViewController {
}
}
/* callback */
public void setListener(UnlockListener listener) {
this.listener = Optional.ofNullable(listener);
}
@FunctionalInterface
interface UnlockListener {
void didUnlock(Vault vault);
}
}

View File

@@ -10,7 +10,6 @@ package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
@@ -46,12 +45,11 @@ import javafx.stage.PopupWindow.AnchorLocation;
import javafx.stage.Stage;
import javafx.util.Duration;
public class UnlockedController extends AbstractFXMLViewController {
public class UnlockedController extends LocalizedFXMLViewController {
private static final int IO_SAMPLING_STEPS = 100;
private static final double IO_SAMPLING_INTERVAL = 0.25;
private final Localization localization;
private final Stage macWarningsWindow = new Stage();
private final MacWarningsController macWarningsController;
private final ExecutorService exec;
@@ -61,7 +59,7 @@ public class UnlockedController extends AbstractFXMLViewController {
@Inject
public UnlockedController(Localization localization, Provider<MacWarningsController> macWarningsControllerProvider, ExecutorService exec) {
this.localization = localization;
super(localization);
this.macWarningsController = macWarningsControllerProvider.get();
this.exec = exec;
@@ -97,11 +95,6 @@ public class UnlockedController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/unlocked.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
private void vaultChanged(Vault newVault) {
if (newVault == null) {
return;
@@ -261,10 +254,6 @@ public class UnlockedController extends AbstractFXMLViewController {
/* callback */
public LockListener getListener() {
return listener.orElse(null);
}
public void setListener(LockListener listener) {
this.listener = Optional.ofNullable(listener);
}

View File

@@ -0,0 +1,128 @@
package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import org.cryptomator.ui.model.UpgradeInstruction;
import org.cryptomator.ui.model.UpgradeInstruction.UpgradeFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
public class UpgradeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeController.class);
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private final ExecutorService exec;
private final Binding<Optional<UpgradeInstruction>> upgradeInstruction = EasyBind.monadic(vault).map(Vault::availableUpgrade);
private Optional<UpgradeListener> listener = Optional.empty();
@Inject
public UpgradeController(Localization localization, ExecutorService exec) {
super(localization);
this.exec = exec;
}
@FXML
private Label upgradeLabel;
@FXML
private Button upgradeButton;
@FXML
private ProgressIndicator progressIndicator;
@FXML
private Label errorLabel;
@Override
protected void initialize() {
upgradeLabel.textProperty().bind(EasyBind.monadic(upgradeInstruction).map(instruction -> {
return instruction.map(this::upgradeNotification).orElse("");
}).orElse(""));
EasyBind.subscribe(vault, this::vaultChanged);
}
@Override
protected URL getFxmlResourceUrl() {
return getClass().getResource("/fxml/upgrade.fxml");
}
private void vaultChanged(Vault newVault) {
errorLabel.setText(null);
}
// ****************************************
// Upgrade label
// ****************************************
private String upgradeNotification(UpgradeInstruction instruction) {
return instruction.getNotification(vault.get(), localization);
}
// ****************************************
// Upgrade button
// ****************************************
@FXML
private void didClickUpgradeButton(ActionEvent event) {
upgradeInstruction.getValue().ifPresent(this::upgrade);
}
private void upgrade(UpgradeInstruction instruction) {
Vault v = vault.getValue();
Objects.requireNonNull(v);
progressIndicator.setVisible(true);
upgradeButton.setDisable(true);
exec.submit(() -> {
if (!instruction.isApplicable(v)) {
LOG.error("No upgrade needed for " + v.path().getValue());
throw new IllegalStateException("No ugprade needed for " + v.path().getValue());
}
try {
instruction.upgrade(v, localization);
Platform.runLater(() -> {
progressIndicator.setVisible(false);
upgradeButton.setDisable(false);
listener.ifPresent(UpgradeListener::didUpgrade);
});
} catch (UpgradeFailedException e) {
Platform.runLater(() -> {
errorLabel.setText(e.getLocalizedMessage());
progressIndicator.setVisible(false);
upgradeButton.setDisable(false);
});
}
});
}
/* callback */
public void setListener(UpgradeListener listener) {
this.listener = Optional.ofNullable(listener);
}
@FunctionalInterface
interface UpgradeListener {
void didUpgrade();
}
}

View File

@@ -15,7 +15,6 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
@@ -48,20 +47,19 @@ import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
@Singleton
public class WelcomeController extends AbstractFXMLViewController {
public class WelcomeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
private final Application app;
private final Localization localization;
private final Settings settings;
private final Comparator<String> semVerComparator;
private final ExecutorService executor;
@Inject
public WelcomeController(Application app, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator, ExecutorService executor) {
super(localization);
this.app = app;
this.localization = localization;
this.settings = settings;
this.semVerComparator = semVerComparator;
this.executor = executor;
@@ -93,11 +91,6 @@ public class WelcomeController extends AbstractFXMLViewController {
return getClass().getResource("/fxml/welcome.fxml");
}
@Override
protected ResourceBundle getFxmlResourceBundle() {
return localization;
}
// ****************************************
// Check for updates
// ****************************************

View File

@@ -9,9 +9,8 @@
package org.cryptomator.ui.controls;
import org.cryptomator.ui.model.Vault;
import org.fxmisc.easybind.EasyBind;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ContextMenu;
@@ -21,15 +20,18 @@ 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;
import javafx.scene.shape.Circle;
public class DirectoryListCell extends DraggableListCell<Vault>implements ChangeListener<Boolean> {
public class DirectoryListCell extends DraggableListCell<Vault> {
// fill: #FD4943, stroke: #E1443F
private static final Color RED_FILL = Color.rgb(253, 73, 67);
private static final Color RED_STROKE = Color.rgb(225, 68, 63);
// fill: #FFBF2F, stroke: #E4AC36
// private static final Color YELLOW_FILL = Color.rgb(255, 191, 47);
// private static final Color YELLOW_STROKE = Color.rgb(228, 172, 54);
// fill: #28CA40, stroke: #30B740
private static final Color GREEN_FILL = Color.rgb(40, 202, 64);
private static final Color GREEN_STROKE = Color.rgb(48, 183, 64);
@@ -46,58 +48,38 @@ public class DirectoryListCell extends DraggableListCell<Vault>implements Change
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");
statusIndicator.fillProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).map(unlocked -> {
return unlocked ? GREEN_FILL : RED_FILL;
}));
statusIndicator.strokeProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).map(unlocked -> {
return unlocked ? GREEN_STROKE : RED_STROKE;
}));
tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::path).map(p -> new Tooltip(p.toString())));
contextMenuProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).map(unlocked -> {
return unlocked ? null : vaultContextMenu;
}));
setGraphic(hbox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
@Override
protected void updateItem(Vault item, boolean empty) {
final Vault oldItem = super.getItem();
if (oldItem != null) {
oldItem.unlockedProperty().removeListener(this);
}
super.updateItem(item, empty);
if (item == null) {
nameText.setText(null);
pathText.setText(null);
setTooltip(null);
setContextMenu(null);
statusIndicator.setVisible(false);
} else {
nameText.setText(item.getName());
pathText.setText(item.getDisplayablePath());
setTooltip(new Tooltip(item.getPath().toString()));
statusIndicator.setVisible(true);
item.unlockedProperty().addListener(this);
updateStatusIndicator();
updateContextMenu();
}
}
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
updateStatusIndicator();
updateContextMenu();
}
private void updateStatusIndicator() {
final Paint fillColor = getItem().isUnlocked() ? GREEN_FILL : RED_FILL;
final Paint strokeColor = getItem().isUnlocked() ? GREEN_STROKE : RED_STROKE;
statusIndicator.setFill(fillColor);
statusIndicator.setStroke(strokeColor);
}
private void updateContextMenu() {
if (getItem().isUnlocked()) {
this.setContextMenu(null);
} else {
this.setContextMenu(vaultContextMenu);
}
}

View File

@@ -0,0 +1,40 @@
package org.cryptomator.ui.model;
import org.cryptomator.ui.settings.Localization;
public interface UpgradeInstruction {
static UpgradeInstruction[] AVAILABLE_INSTRUCTIONS = {new UpgradeVersion3DropBundleExtension()};
/**
* @return Localized string to display to the user when an upgrade is needed.
*/
String getNotification(Vault vault, Localization localization);
/**
* Upgrades a vault. Might take a moment, should be run in a background thread.
*/
void upgrade(Vault vault, Localization localization) 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.
*/
boolean isApplicable(Vault vault);
/**
* Thrown when data migration failed.
*/
public class UpgradeFailedException extends Exception {
UpgradeFailedException() {
}
UpgradeFailedException(String message) {
super(message);
}
}
}

View File

@@ -0,0 +1,55 @@
package org.cryptomator.ui.model;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.ui.settings.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
@Override
public String getNotification(Vault vault, Localization localization) {
String fmt = localization.getString("upgrade.version3dropBundleExtension.msg");
Path path = vault.path().getValue();
String oldVaultName = path.getFileName().toString();
String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION);
return String.format(fmt, oldVaultName, newVaultName);
}
@Override
public void upgrade(Vault vault, Localization localization) throws UpgradeFailedException {
Path path = vault.path().getValue();
String oldVaultName = path.getFileName().toString();
String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION);
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 {
Files.move(path, path.resolveSibling(newVaultName));
Platform.runLater(() -> {
vault.setPath(newPath);
});
} catch (IOException e) {
LOG.error("Vault migration failed", e);
throw new UpgradeFailedException();
}
}
}
@Override
public boolean isApplicable(Vault vault) {
return vault.path().getValue().getFileName().toString().endsWith(Vault.VAULT_FILE_EXTENSION);
}
}

View File

@@ -11,10 +11,12 @@ package org.cryptomator.ui.model;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
@@ -42,12 +44,17 @@ import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.DeferredClosable;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.FXThreads;
import org.fxmisc.easybind.EasyBind;
import com.google.common.collect.ImmutableMap;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -55,7 +62,7 @@ public class Vault implements CryptoFileSystemDelegate {
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
private final Path path;
private final ObjectProperty<Path> path;
private final DeferredCloser closer;
private final ShorteningFileSystemFactory shorteningFileSystemFactory;
private final CryptoFileSystemFactory cryptoFileSystemFactory;
@@ -73,20 +80,20 @@ public class Vault implements CryptoFileSystemDelegate {
* Package private constructor, use {@link VaultFactory}.
*/
Vault(Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
this.path = vaultDirectoryPath;
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
this.closer = closer;
this.shorteningFileSystemFactory = shorteningFileSystemFactory;
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
try {
setMountName(getName());
setMountName(name().getValue());
} catch (IllegalArgumentException e) {
// mount name needs to be set by the user explicitly later
}
}
private FileSystem getNioFileSystem() {
return LazyInitializer.initializeLazily(nioFileSystem, () -> NioFileSystem.rootedAt(path));
return LazyInitializer.initializeLazily(nioFileSystem, () -> NioFileSystem.rootedAt(path.getValue()));
}
// ******************************************************************************
@@ -158,6 +165,16 @@ public class Vault implements CryptoFileSystemDelegate {
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
}
public boolean needsUpgrade() {
return availableUpgrade().isPresent();
}
public Optional<UpgradeInstruction> availableUpgrade() {
return Arrays.stream(UpgradeInstruction.AVAILABLE_INSTRUCTIONS).filter(instruction -> {
return instruction.isApplicable(this);
}).findAny();
}
// ******************************************************************************
// Delegate methods
// ********************************************************************************/
@@ -180,31 +197,42 @@ public class Vault implements CryptoFileSystemDelegate {
return filesystemFrontend.get().map(Frontend::getWebDavUrl).orElseThrow(IllegalStateException::new);
}
public Path getPath() {
void setPath(Path path) {
this.path.set(path);
this.nioFileSystem.set(null);
}
public ReadOnlyObjectProperty<Path> path() {
return path;
}
public String getDisplayablePath() {
public Binding<String> displayablePath() {
Path homeDir = Paths.get(SystemUtils.USER_HOME);
if (path.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(path);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
return homePrefix + relativePath.toString();
} else {
return path.toString();
}
return EasyBind.map(path, p -> {
if (p.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
return homePrefix + relativePath.toString();
} else {
return path.toString();
}
});
}
/**
* @return Directory name without preceeding path components and file extension
*/
public String getName() {
return StringUtils.removeEnd(path.getFileName().toString(), VAULT_FILE_EXTENSION);
public Binding<String> name() {
return EasyBind.map(path, p -> p.getFileName().toString());
}
public boolean doesVaultDirectoryExist() {
return Files.isDirectory(path.getValue());
}
public boolean isValidVaultDirectory() {
try {
return cryptoFileSystemFactory.isValidVaultStructure(getNioFileSystem());
return doesVaultDirectoryExist() && cryptoFileSystemFactory.isValidVaultStructure(getNioFileSystem());
} catch (UncheckedIOException e) {
return false;
}
@@ -292,17 +320,17 @@ public class Vault implements CryptoFileSystemDelegate {
@Override
public int hashCode() {
return path.hashCode();
return path.getValue().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Vault) {
final Vault other = (Vault) obj;
return this.path.equals(other.path);
return this.path.getValue().equals(other.path.getValue());
} else {
return false;
}
}
}
}

View File

@@ -55,7 +55,7 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
@Override
public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("path", value.getPath().toString());
jgen.writeStringField("path", value.path().getValue().toString());
jgen.writeStringField("mountName", value.getMountName());
final Character winDriveLetter = value.getWinDriveLetter();
if (winDriveLetter != null) {

View File

@@ -77,7 +77,6 @@ public class SettingsProvider implements Provider<Settings> {
final Path settingsPath = getSettingsPath();
final InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ);
settings = objectMapper.readValue(in, Settings.class);
settings.getDirectories().removeIf(v -> !v.isValidVaultDirectory());
} catch (IOException e) {
LOG.warn("Failed to load settings, creating new one.");
settings = new Settings();

View File

@@ -0,0 +1,20 @@
<?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 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

@@ -0,0 +1,27 @@
<?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 javafx.scene.layout.VBox?>
<VBox prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label fx:id="upgradeLabel" textAlignment="CENTER" wrapText="true"/>
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
<Label fx:id="errorLabel" textAlignment="CENTER" wrapText="true"/>
</VBox>

View File

@@ -27,6 +27,15 @@ initialize.button.ok=Create vault
initialize.messageLabel.alreadyInitialized=Vault already initialized
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
# notfound.fxml
notfound.label=Vault couldn't be found. Has it been moved?
# upgrade.fxml
upgrade.button=Upgrade vault
upgrade.version3dropBundleExtension.msg=This vault needs to be migrated to a newer format.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
upgrade.version3dropBundleExtension.err.alreadyExists=Automatic migration failed.\n"%s" already exists.
# unlock.fxml
unlock.label.password=Password
unlock.label.mountName=Drive name

View File

@@ -27,6 +27,15 @@ initialize.button.ok=Tresor erstellen
initialize.messageLabel.alreadyInitialized=Tresor bereits vorhanden
initialize.messageLabel.initializationFailed=Fehler beim Initialisieren. Details in der Log-Datei.
# notfound.fxml
notfound.label=Tresor konnte nicht gefunden werden.\nWurde er verschoben?
# upgrade.fxml
upgrade.button=Tresor aktualisieren
upgrade.version3dropBundleExtension.msg=Dieser Tresor muss auf ein neueres Format aktualisiert werden.\n"%1$s" wird in "%2$s" umbenannt.\nStellen Sie bitte sicher, dass derzeit keine Synchronisation stattfindet.
upgrade.version3dropBundleExtension.err.alreadyExists=Migration fehlgeschlagen.\n"%s" existiert bereits.
# unlock.fxml
unlock.label.password=Passwort
unlock.label.mountName=Laufwerksname