diff --git a/pom.xml b/pom.xml
index c216885d7..d9feb3fe5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,7 +44,7 @@
31.1-jre
2.41
2.9.0
- 1.5.2
+ 1.6.0
1.7.36
1.2.11
diff --git a/src/main/java/org/cryptomator/common/settings/UiTheme.java b/src/main/java/org/cryptomator/common/settings/UiTheme.java
index 62fe714e4..24e73dad6 100644
--- a/src/main/java/org/cryptomator/common/settings/UiTheme.java
+++ b/src/main/java/org/cryptomator/common/settings/UiTheme.java
@@ -3,9 +3,9 @@ package org.cryptomator.common.settings;
import org.apache.commons.lang3.SystemUtils;
public enum UiTheme {
- LIGHT("preferences.general.theme.light"), //
- DARK("preferences.general.theme.dark"), //
- AUTOMATIC("preferences.general.theme.automatic");
+ LIGHT("preferences.interface.theme.light"), //
+ DARK("preferences.interface.theme.dark"), //
+ AUTOMATIC("preferences.interface.theme.automatic");
public static UiTheme[] applicableValues() {
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 90f391ec6..3cd5ec727 100644
--- a/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -21,15 +21,18 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.application.Application;
import javafx.stage.Stage;
+import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executors;
@Singleton
public class Cryptomator {
+ private static final long STARTUP_TIME = System.currentTimeMillis();
// DaggerCryptomatorComponent gets generated by Dagger.
// Run Maven and include target/generated-sources/annotations in your IDE.
- private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
+ private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
private final LoggerConfiguration logConfig;
@@ -50,6 +53,20 @@ public class Cryptomator {
}
public static void main(String[] args) {
+ var printVersion = Optional.ofNullable(args) //
+ .stream() //Streams either one element (the args-array) or zero elements
+ .flatMap(Arrays::stream) //
+ .anyMatch(arg -> "-v".equals(arg) || "--version".equals(arg));
+
+ if (printVersion) {
+ var appVer = System.getProperty("cryptomator.appVersion", "SNAPSHOT");
+ var buildNumber = System.getProperty("cryptomator.buildNumber", "SNAPSHOT");
+
+ //Reduce noise for parsers by using System.out directly
+ System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber);
+ return;
+ }
+
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode);
System.exit(exitCode); // end remaining non-daemon threads.
@@ -63,6 +80,7 @@ public class Cryptomator {
*/
private int run(String[] args) {
logConfig.init();
+ LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME);
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
debugMode.initialize();
supportedLanguages.applyPreferred();
@@ -112,7 +130,7 @@ public class Cryptomator {
@Override
public void start(Stage primaryStage) {
- LOG.info("JavaFX application started.");
+ LOG.info("JavaFX runtime started after {}ms", System.currentTimeMillis() - STARTUP_TIME);
FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() //
.fxApplication(this) //
.primaryStage(primaryStage) //
diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
index b43c0eca0..8c6443717 100644
--- a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
+++ b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
@@ -1,10 +1,12 @@
package org.cryptomator.launcher;
+import dagger.BindsInstance;
import dagger.Component;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.logging.LoggerModule;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
+import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
@@ -15,4 +17,9 @@ public interface CryptomatorComponent {
FxApplicationComponent.Builder fxAppComponentBuilder();
+ @Component.Factory
+ interface Factory {
+ CryptomatorComponent create(@BindsInstance @Named("startupTime") long startupTime);
+ }
+
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
index b810a817f..3ddb7cba6 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
@@ -7,6 +7,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javax.inject.Named;
import javafx.application.Platform;
@FxApplicationScoped
@@ -14,6 +15,7 @@ public class FxApplication {
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
+ private final long startupTime;
private final Settings settings;
private final AppLaunchEventHandler launchEventHandler;
private final Lazy trayMenu;
@@ -23,7 +25,8 @@ public class FxApplication {
private final AutoUnlocker autoUnlocker;
@Inject
- FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
+ FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
+ this.startupTime = startupTime;
this.settings = settings;
this.launchEventHandler = launchEventHandler;
this.trayMenu = trayMenu;
@@ -58,6 +61,10 @@ public class FxApplication {
stage.setIconified(true);
}
}
+ LOG.debug("Main window initialized after {}ms", System.currentTimeMillis() - startupTime);
+ }).exceptionally(error -> {
+ LOG.error("Failed to show main window", error);
+ return null;
});
launchEventHandler.startHandlingLaunchEvents();
diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
index 50826f4c4..d33e919b6 100644
--- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
+++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
@@ -1,38 +1,26 @@
package org.cryptomator.ui.preferences;
-import com.google.common.base.Strings;
import org.cryptomator.common.Environment;
-import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
-import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
-import org.cryptomator.launcher.SupportedLanguages;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
-import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
-import javafx.geometry.NodeOrientation;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
-import javafx.scene.control.RadioButton;
-import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.util.List;
-import java.util.Locale;
import java.util.Optional;
-import java.util.ResourceBundle;
@PreferencesScoped
public class GeneralPreferencesController implements FxController {
@@ -41,39 +29,23 @@ public class GeneralPreferencesController implements FxController {
private final Stage window;
private final Settings settings;
- private final boolean trayMenuInitialized;
- private final boolean trayMenuSupported;
private final Optional autoStartProvider;
- private final ObjectProperty selectedTabProperty;
- private final LicenseHolder licenseHolder;
- private final ResourceBundle resourceBundle;
private final Application application;
private final Environment environment;
private final List keychainAccessProviders;
private final FxApplicationWindows appWindows;
- public ChoiceBox themeChoiceBox;
public ChoiceBox keychainBackendChoiceBox;
- public CheckBox showMinimizeButtonCheckbox;
- public CheckBox showTrayIconCheckbox;
public CheckBox startHiddenCheckbox;
- public ChoiceBox preferredLanguageChoiceBox;
public CheckBox debugModeCheckbox;
public CheckBox autoStartCheckbox;
public ToggleGroup nodeOrientation;
- public RadioButton nodeOrientationLtr;
- public RadioButton nodeOrientationRtl;
@Inject
- GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, List keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) {
+ GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional autoStartProvider, List keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) {
this.window = window;
this.settings = settings;
- this.trayMenuInitialized = trayMenu.isInitialized();
- this.trayMenuSupported = trayMenu.isSupported();
this.autoStartProvider = autoStartProvider;
this.keychainAccessProviders = keychainAccessProviders;
- this.selectedTabProperty = selectedTabProperty;
- this.licenseHolder = licenseHolder;
- this.resourceBundle = resourceBundle;
this.application = application;
this.environment = environment;
this.appWindows = appWindows;
@@ -81,32 +53,12 @@ public class GeneralPreferencesController implements FxController {
@FXML
public void initialize() {
- themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
- if (!themeChoiceBox.getItems().contains(settings.theme().get())) {
- settings.theme().set(UiTheme.LIGHT);
- }
- themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
- themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
-
- showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton());
-
- showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
-
startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden());
- preferredLanguageChoiceBox.getItems().add(null);
- preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS);
- preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty());
- preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
-
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
- nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
- nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
- nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
-
var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders);
keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders);
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get()));
@@ -114,29 +66,10 @@ public class GeneralPreferencesController implements FxController {
Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
}
-
- public boolean isTrayMenuInitialized() {
- return trayMenuInitialized;
- }
-
- public boolean isTrayMenuSupported() {
- return trayMenuSupported;
- }
-
public boolean isAutoStartSupported() {
return autoStartProvider.isPresent();
}
- private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
- if (nodeOrientationLtr.equals(newValue)) {
- settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT);
- } else if (nodeOrientationRtl.equals(newValue)) {
- settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT);
- } else {
- LOG.warn("Unexpected toggle option {}", newValue);
- }
- }
-
@FXML
public void toggleAutoStart() {
autoStartProvider.ifPresent(autoStart -> {
@@ -155,16 +88,6 @@ public class GeneralPreferencesController implements FxController {
});
}
- public LicenseHolder getLicenseHolder() {
- return licenseHolder;
- }
-
-
- @FXML
- public void showContributeTab() {
- selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE);
- }
-
@FXML
public void showLogfileDirectory() {
environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString()));
@@ -172,53 +95,7 @@ public class GeneralPreferencesController implements FxController {
/* Helper classes */
- private static class UiThemeConverter extends StringConverter {
-
- private final ResourceBundle resourceBundle;
-
- UiThemeConverter(ResourceBundle resourceBundle) {
- this.resourceBundle = resourceBundle;
- }
-
- @Override
- public String toString(UiTheme impl) {
- return resourceBundle.getString(impl.getDisplayName());
- }
-
- @Override
- public UiTheme fromString(String string) {
- throw new UnsupportedOperationException();
- }
-
- }
-
- private static class LanguageTagConverter extends StringConverter {
-
- private final ResourceBundle resourceBundle;
-
- LanguageTagConverter(ResourceBundle resourceBundle) {
- this.resourceBundle = resourceBundle;
- }
-
- @Override
- public String toString(String tag) {
- if (tag == null) {
- return resourceBundle.getString("preferences.general.language.auto");
- } else {
- var locale = Locale.forLanguageTag(tag);
- var lang = locale.getDisplayLanguage(locale);
- var region = locale.getDisplayCountry(locale);
- return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")");
- }
- }
-
- @Override
- public String fromString(String displayLanguage) {
- throw new UnsupportedOperationException();
- }
- }
-
- private class KeychainProviderDisplayNameConverter extends StringConverter {
+ private static class KeychainProviderDisplayNameConverter extends StringConverter {
@Override
public String toString(KeychainAccessProvider provider) {
diff --git a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
new file mode 100644
index 000000000..dd463b9e3
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java
@@ -0,0 +1,156 @@
+package org.cryptomator.ui.preferences;
+
+import com.google.common.base.Strings;
+import org.cryptomator.common.LicenseHolder;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.UiTheme;
+import org.cryptomator.launcher.SupportedLanguages;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.traymenu.TrayMenuComponent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.fxml.FXML;
+import javafx.geometry.NodeOrientation;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ChoiceBox;
+import javafx.scene.control.RadioButton;
+import javafx.scene.control.Toggle;
+import javafx.scene.control.ToggleGroup;
+import javafx.util.StringConverter;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+@PreferencesScoped
+public class InterfacePreferencesController implements FxController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InterfacePreferencesController.class);
+
+ private final Settings settings;
+ private final boolean trayMenuInitialized;
+ private final boolean trayMenuSupported;
+ private final ObjectProperty selectedTabProperty;
+ private final LicenseHolder licenseHolder;
+ private final ResourceBundle resourceBundle;
+ public ChoiceBox themeChoiceBox;
+ public CheckBox showMinimizeButtonCheckbox;
+ public CheckBox showTrayIconCheckbox;
+ public ChoiceBox preferredLanguageChoiceBox;
+ public ToggleGroup nodeOrientation;
+ public RadioButton nodeOrientationLtr;
+ public RadioButton nodeOrientationRtl;
+
+ @Inject
+ InterfacePreferencesController(Settings settings, TrayMenuComponent trayMenu, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle) {
+ this.settings = settings;
+ this.trayMenuInitialized = trayMenu.isInitialized();
+ this.trayMenuSupported = trayMenu.isSupported();
+ this.selectedTabProperty = selectedTabProperty;
+ this.licenseHolder = licenseHolder;
+ this.resourceBundle = resourceBundle;
+ }
+
+ @FXML
+ public void initialize() {
+ themeChoiceBox.getItems().addAll(UiTheme.applicableValues());
+ if (!themeChoiceBox.getItems().contains(settings.theme().get())) {
+ settings.theme().set(UiTheme.LIGHT);
+ }
+ themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
+ themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
+
+ showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton());
+
+ showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
+
+ preferredLanguageChoiceBox.getItems().add(null);
+ preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS);
+ preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty());
+ preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
+
+ nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
+ nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
+ nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
+ }
+
+
+ public boolean isTrayMenuInitialized() {
+ return trayMenuInitialized;
+ }
+
+ public boolean isTrayMenuSupported() {
+ return trayMenuSupported;
+ }
+
+ private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
+ if (nodeOrientationLtr.equals(newValue)) {
+ settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT);
+ } else if (nodeOrientationRtl.equals(newValue)) {
+ settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT);
+ } else {
+ LOG.warn("Unexpected toggle option {}", newValue);
+ }
+ }
+
+ public LicenseHolder getLicenseHolder() {
+ return licenseHolder;
+ }
+
+
+ @FXML
+ public void showContributeTab() {
+ selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE);
+ }
+
+ /* Helper classes */
+
+ private static class UiThemeConverter extends StringConverter {
+
+ private final ResourceBundle resourceBundle;
+
+ UiThemeConverter(ResourceBundle resourceBundle) {
+ this.resourceBundle = resourceBundle;
+ }
+
+ @Override
+ public String toString(UiTheme impl) {
+ return resourceBundle.getString(impl.getDisplayName());
+ }
+
+ @Override
+ public UiTheme fromString(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ private static class LanguageTagConverter extends StringConverter {
+
+ private final ResourceBundle resourceBundle;
+
+ LanguageTagConverter(ResourceBundle resourceBundle) {
+ this.resourceBundle = resourceBundle;
+ }
+
+ @Override
+ public String toString(String tag) {
+ if (tag == null) {
+ return resourceBundle.getString("preferences.interface.language.auto");
+ } else {
+ var locale = Locale.forLanguageTag(tag);
+ var lang = locale.getDisplayLanguage(locale);
+ var region = locale.getDisplayCountry(locale);
+ return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")");
+ }
+ }
+
+ @Override
+ public String fromString(String displayLanguage) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
index 276794753..caaaf7d80 100644
--- a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
+++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java
@@ -24,6 +24,7 @@ public class PreferencesController implements FxController {
private final BooleanBinding updateAvailable;
public TabPane tabPane;
public Tab generalTab;
+ public Tab interfaceTab;
public Tab volumeTab;
public Tab updatesTab;
public Tab contributeTab;
@@ -50,10 +51,11 @@ public class PreferencesController implements FxController {
private Tab getTabToSelect(SelectedPreferencesTab selectedTab) {
return switch (selectedTab) {
- case UPDATES -> updatesTab;
- case VOLUME -> volumeTab;
- case CONTRIBUTE -> contributeTab;
case GENERAL -> generalTab;
+ case INTERFACE -> interfaceTab;
+ case VOLUME -> volumeTab;
+ case UPDATES -> updatesTab;
+ case CONTRIBUTE -> contributeTab;
case ABOUT -> aboutTab;
case ANY -> updateAvailable.get() ? updatesTab : generalTab;
};
diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
index 189666591..858f8f1d8 100644
--- a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
+++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
@@ -64,6 +64,11 @@ abstract class PreferencesModule {
@FxControllerKey(GeneralPreferencesController.class)
abstract FxController bindGeneralPreferencesController(GeneralPreferencesController controller);
+ @Binds
+ @IntoMap
+ @FxControllerKey(InterfacePreferencesController.class)
+ abstract FxController bindInterfacePreferencesController(InterfacePreferencesController controller);
+
@Binds
@IntoMap
@FxControllerKey(UpdatesPreferencesController.class)
diff --git a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java
index 892d16a8c..00b519493 100644
--- a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java
+++ b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java
@@ -11,6 +11,11 @@ public enum SelectedPreferencesTab {
*/
GENERAL,
+ /**
+ * Show interface tab
+ */
+ INTERFACE,
+
/**
* Show volume tab
*/
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java
index 311f5746e..a1180dd9f 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java
@@ -7,6 +7,7 @@ import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
+import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -16,6 +17,7 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Collection;
+import java.util.function.Predicate;
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -102,12 +104,29 @@ public class RecoveryKeyFactory {
* @return true if this seems to be a legitimate recovery key
*/
public boolean validateRecoveryKey(String recoveryKey) {
+ return validateRecoveryKey(recoveryKey, null);
+ }
+
+ /**
+ * Checks whether a String is a syntactically correct recovery key with a valid checksum and passes the extended validation.
+ *
+ * @param recoveryKey A word sequence which might be a recovery key
+ * @param extendedValidation Additional verification of the decoded key (optional)
+ * @return true if this seems to be a legitimate recovery key and passes the extended validation
+ */
+ public boolean validateRecoveryKey(String recoveryKey, @Nullable Predicate extendedValidation) {
+ byte[] key = new byte[0];
try {
- byte[] key = decodeRecoveryKey(recoveryKey);
- Arrays.fill(key, (byte) 0x00);
- return true;
+ key = decodeRecoveryKey(recoveryKey);
+ if (extendedValidation != null) {
+ return extendedValidation.test(key);
+ } else {
+ return true;
+ }
} catch (IllegalArgumentException e) {
return false;
+ } finally {
+ Arrays.fill(key, (byte) 0x00);
}
}
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
index b30167e73..0522e3a8d 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java
@@ -4,7 +4,9 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
@@ -22,12 +24,25 @@ import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
+import java.io.IOException;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class RecoveryKeyModule {
+ @Provides
+ @Nullable
+ @RecoveryKeyWindow
+ @RecoveryKeyScoped
+ static VaultConfig.UnverifiedVaultConfig vaultConfig(@RecoveryKeyWindow Vault vault) {
+ try {
+ return vault.getVaultConfigCache().get();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
@Provides
@RecoveryKeyWindow
@RecoveryKeyScoped
diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
index 7e40a833b..e05a73169 100644
--- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
+++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java
@@ -3,10 +3,16 @@ package org.cryptomator.ui.recoverykey;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import dagger.Lazy;
+import org.cryptomator.common.Nullable;
import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.VaultConfigLoadException;
+import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
@@ -24,10 +30,12 @@ import java.util.Optional;
@RecoveryKeyScoped
public class RecoveryKeyRecoverController implements FxController {
- private final static CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
+ private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
+ private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
private final Stage window;
private final Vault vault;
+ private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig;
private final StringProperty recoveryKey;
private final RecoveryKeyFactory recoveryKeyFactory;
private final BooleanBinding validRecoveryKey;
@@ -37,9 +45,10 @@ public class RecoveryKeyRecoverController implements FxController {
public TextArea textarea;
@Inject
- public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene) {
+ public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene) {
this.window = window;
this.vault = vault;
+ this.unverifiedVaultConfig = unverifiedVaultConfig;
this.recoveryKey = recoveryKey;
this.recoveryKeyFactory = recoveryKeyFactory;
this.validRecoveryKey = Bindings.createBooleanBinding(this::isValidRecoveryKey, recoveryKey);
@@ -96,6 +105,20 @@ public class RecoveryKeyRecoverController implements FxController {
window.setScene(resetPasswordScene.get());
}
+ private boolean checkKeyAgainstVaultConfig(byte[] key) {
+ try {
+ var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion());
+ LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId());
+ return true;
+ } catch (VaultKeyInvalidException e) {
+ LOG.debug("Provided recovery key does not match vault config signature.");
+ return false;
+ } catch (VaultConfigLoadException e) {
+ LOG.error("Failed to parse vault config", e);
+ return false;
+ }
+ }
+
/* Getter/Setter */
public Vault getVault() {
@@ -107,7 +130,11 @@ public class RecoveryKeyRecoverController implements FxController {
}
public boolean isValidRecoveryKey() {
- return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get());
+ if (unverifiedVaultConfig != null) {
+ return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), this::checkKeyAgainstVaultConfig);
+ } else {
+ return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get());
+ }
}
public TextFormatter getRecoveryKeyTextFormatter() {
diff --git a/src/main/resources/fxml/preferences.fxml b/src/main/resources/fxml/preferences.fxml
index 4eaf3d45c..c96eb5395 100644
--- a/src/main/resources/fxml/preferences.fxml
+++ b/src/main/resources/fxml/preferences.fxml
@@ -9,7 +9,7 @@
fx:controller="org.cryptomator.ui.preferences.PreferencesController"
minWidth="-Infinity"
maxWidth="-Infinity"
- prefWidth="500"
+ prefWidth="650"
tabMinWidth="60"
tabClosingPolicy="UNAVAILABLE"
tabDragPolicy="FIXED">
@@ -22,6 +22,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml
index cfa8ec010..ce2a31e4e 100644
--- a/src/main/resources/fxml/preferences_about.fxml
+++ b/src/main/resources/fxml/preferences_about.fxml
@@ -11,9 +11,9 @@
+ spacing="24">
-
+
diff --git a/src/main/resources/fxml/preferences_contribute.fxml b/src/main/resources/fxml/preferences_contribute.fxml
index bfe91d0eb..55d40b495 100644
--- a/src/main/resources/fxml/preferences_contribute.fxml
+++ b/src/main/resources/fxml/preferences_contribute.fxml
@@ -13,9 +13,9 @@
+ spacing="24">
-
+
diff --git a/src/main/resources/fxml/preferences_general.fxml b/src/main/resources/fxml/preferences_general.fxml
index 1e3be74c6..6d2b68447 100644
--- a/src/main/resources/fxml/preferences_general.fxml
+++ b/src/main/resources/fxml/preferences_general.fxml
@@ -5,54 +5,35 @@
-
+
+ spacing="12">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/preferences_interface.fxml b/src/main/resources/fxml/preferences_interface.fxml
new file mode 100644
index 000000000..6cb9b7d73
--- /dev/null
+++ b/src/main/resources/fxml/preferences_interface.fxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml
index 0b176dfb3..3156d1c3c 100644
--- a/src/main/resources/fxml/preferences_updates.fxml
+++ b/src/main/resources/fxml/preferences_updates.fxml
@@ -11,19 +11,19 @@
+ spacing="12">
-
+
-
+