mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-21 10:06:55 -04:00
Merge branch 'develop' into feature/integrations-api-1.1.0
# Conflicts: # src/main/java/org/cryptomator/ui/fxapp/FxApplication.java # src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) //
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<TrayMenuComponent> trayMenu;
|
||||
@@ -23,7 +25,8 @@ public class FxApplication {
|
||||
private final AutoUnlocker autoUnlocker;
|
||||
|
||||
@Inject
|
||||
FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
|
||||
FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> 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();
|
||||
|
||||
@@ -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> autoStartProvider;
|
||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
private final List<KeychainAccessProvider> keychainAccessProviders;
|
||||
private final FxApplicationWindows appWindows;
|
||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
||||
public CheckBox showMinimizeButtonCheckbox;
|
||||
public CheckBox showTrayIconCheckbox;
|
||||
public CheckBox startHiddenCheckbox;
|
||||
public ChoiceBox<String> 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> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) {
|
||||
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> 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<UiTheme> {
|
||||
|
||||
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<String> {
|
||||
|
||||
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<KeychainAccessProvider> {
|
||||
private static class KeychainProviderDisplayNameConverter extends StringConverter<KeychainAccessProvider> {
|
||||
|
||||
@Override
|
||||
public String toString(KeychainAccessProvider provider) {
|
||||
|
||||
@@ -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<SelectedPreferencesTab> selectedTabProperty;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ResourceBundle resourceBundle;
|
||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||
public CheckBox showMinimizeButtonCheckbox;
|
||||
public CheckBox showTrayIconCheckbox;
|
||||
public ChoiceBox<String> preferredLanguageChoiceBox;
|
||||
public ToggleGroup nodeOrientation;
|
||||
public RadioButton nodeOrientationLtr;
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
InterfacePreferencesController(Settings settings, TrayMenuComponent trayMenu, ObjectProperty<SelectedPreferencesTab> 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<UiTheme> {
|
||||
|
||||
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<String> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,6 +11,11 @@ public enum SelectedPreferencesTab {
|
||||
*/
|
||||
GENERAL,
|
||||
|
||||
/**
|
||||
* Show interface tab
|
||||
*/
|
||||
INTERFACE,
|
||||
|
||||
/**
|
||||
* Show volume tab
|
||||
*/
|
||||
|
||||
@@ -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 <code>true</code> 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 <code>true</code> if this seems to be a legitimate recovery key and passes the extended validation
|
||||
*/
|
||||
public boolean validateRecoveryKey(String recoveryKey, @Nullable Predicate<byte[]> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Scene> 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<Scene> 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() {
|
||||
|
||||
Reference in New Issue
Block a user