Vastly refactored settings, integrated new webdav-nio-adapter snapshot version, allow reconfiguration of IPv6 and Port settings during runtime

This commit is contained in:
Sebastian Stenzel
2017-01-31 22:30:44 +01:00
parent 7750a49e65
commit 02ae2e7ca0
32 changed files with 416 additions and 235 deletions
@@ -8,6 +8,7 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.ui; package org.cryptomator.ui;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -22,12 +23,14 @@ import org.cryptomator.keychain.KeychainModule;
import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.SettingsProvider; import org.cryptomator.ui.settings.SettingsProvider;
import org.cryptomator.ui.util.DeferredCloser; import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import javafx.application.Application; import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.stage.Stage; import javafx.stage.Stage;
@Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class}) @Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class})
@@ -83,8 +86,20 @@ class CryptomatorModule {
@Provides @Provides
@Singleton @Singleton
WebDavServer provideWebDavServer(Settings settings) { Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return WebDavServer.create("localhost", settings.getPort()); return EasyBind.combine(settings.useIpv6(), settings.port(), (useIpv6, port) -> {
String host = useIpv6 ? "::1" : "localhost";
return InetSocketAddress.createUnresolved(host, port.intValue());
});
}
@Provides
@Singleton
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
WebDavServer server = WebDavServer.create();
// no need to unsubscribe eventually, because server is a singleton
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
return server;
} }
} }
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui; package org.cryptomator.ui;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@@ -36,7 +41,7 @@ public class DebugMode {
} }
public void initialize() { public void initialize() {
if (settings.getDebugMode()) { if (settings.debugMode().get()) {
enable(); enable();
LOG.debug("Debug mode initialized"); LOG.debug("Debug mode initialized");
} }
@@ -147,11 +147,11 @@ class ExitUtil {
} }
private void showTrayNotification(TrayIcon trayIcon) { private void showTrayNotification(TrayIcon trayIcon) {
if (settings.getNumTrayNotifications() <= 0) { int remainingTrayNotification = settings.numTrayNotifications().get();
if (remainingTrayNotification <= 0) {
return; return;
} else { } else {
settings.setNumTrayNotifications(settings.getNumTrayNotifications() - 1); settings.numTrayNotifications().set(remainingTrayNotification - 1);
settings.save();
} }
final Runnable notificationCmd; final Runnable notificationCmd;
if (SystemUtils.IS_OS_MAC_OSX) { if (SystemUtils.IS_OS_MAC_OSX) {
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.settings.Localization;
@@ -232,7 +232,7 @@ public class MainController extends LocalizedFXMLViewController {
} }
final VaultSettings vaultSettings = VaultSettings.withRandomId(); final VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.setPath(vaultPath); vaultSettings.path().set(vaultPath);
final Vault vault = vaultFactoy.get(vaultSettings); final Vault vault = vaultFactoy.get(vaultSettings);
if (!vaults.contains(vault)) { if (!vaults.contains(vault)) {
vaults.add(vault); vaults.add(vault);
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import java.net.URL; import java.net.URL;
@@ -18,9 +18,11 @@ import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.settings.Settings;
import org.fxmisc.easybind.EasyBind;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox; import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@@ -44,6 +46,9 @@ public class SettingsController extends LocalizedFXMLViewController {
@FXML @FXML
private TextField portField; private TextField portField;
@FXML
private Button changePortButton;
@FXML @FXML
private Label useIpv6Label; private Label useIpv6Label;
@@ -65,25 +70,26 @@ public class SettingsController extends LocalizedFXMLViewController {
@Override @Override
public void initialize() { public void initialize() {
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally()); checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally()); checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
portField.setText(String.valueOf(settings.getPort())); portField.setText(String.valueOf(settings.port().intValue()));
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents); portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
useIpv6Label.setVisible(SystemUtils.IS_OS_WINDOWS); useIpv6Label.setVisible(SystemUtils.IS_OS_WINDOWS);
useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS); useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS);
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6()); useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.useIpv6().get());
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT"))); versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX); prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX); prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.getItems().add("dav"); prefGvfsScheme.getItems().add("dav");
prefGvfsScheme.getItems().add("webdav"); prefGvfsScheme.getItems().add("webdav");
prefGvfsScheme.setValue(settings.getPreferredGvfsScheme()); prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
debugModeCheckbox.setSelected(settings.getDebugMode()); debugModeCheckbox.setSelected(settings.debugMode().get());
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange); settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
EasyBind.subscribe(portField.textProperty(), this::portDidChange); settings.useIpv6().bind(useIpv6Checkbox.selectedProperty());
EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange); settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
EasyBind.subscribe(prefGvfsScheme.valueProperty(), this::prefGvfsSchemeDidChange); settings.debugMode().bind(debugModeCheckbox.selectedProperty());
EasyBind.subscribe(debugModeCheckbox.selectedProperty(), this::debugModeDidChange);
} }
@Override @Override
@@ -95,40 +101,30 @@ public class SettingsController extends LocalizedFXMLViewController {
return Optional.ofNullable(getClass().getPackage().getImplementationVersion()); return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
} }
private void checkForUpdateDidChange(Boolean newValue) { @FXML
settings.setCheckForUpdatesEnabled(newValue); private void changePort(ActionEvent evt) {
settings.save(); assert isPortValid() : "Button must be disabled, if port is invalid.";
try {
int port = Integer.parseInt(portField.getText());
settings.port().set(port);
} catch (NumberFormatException e) {
throw new IllegalStateException("Button must be disabled, if port is invalid.", e);
}
} }
private void portDidChange(String newValue) { private boolean isPortValid() {
try { try {
int port = Integer.parseInt(newValue); int port = Integer.parseInt(portField.getText());
if (!settings.isPortValid(port)) { if (port == 0 || port >= Settings.MIN_PORT && port <= Settings.MAX_PORT) {
settings.setPort(Settings.DEFAULT_PORT); return true;
} else { } else {
settings.setPort(port); return false;
settings.save();
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
portField.setText(String.valueOf(Settings.DEFAULT_PORT)); return false;
} }
} }
private void useIpv6DidChange(Boolean newValue) {
settings.setUseIpv6(newValue);
settings.save();
}
private void debugModeDidChange(Boolean newValue) {
settings.setDebugMode(newValue);
settings.save();
}
private void prefGvfsSchemeDidChange(String newValue) {
settings.setPreferredGvfsScheme(newValue);
settings.save();
}
private void filterNumericKeyEvents(KeyEvent t) { private void filterNumericKeyEvents(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) { if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return; return;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.controllers; package org.cryptomator.ui.controllers;
import java.net.URL; import java.net.URL;
@@ -82,7 +82,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
public void initialize() { public void initialize() {
if (areUpdatesManagedExternally()) { if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false); checkForUpdatesContainer.setVisible(false);
} else if (settings.isCheckForUpdatesEnabled()) { } else if (settings.checkForUpdates().get()) {
this.checkForUpdates(); this.checkForUpdates();
} }
} }
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.util.Arrays; import java.util.Arrays;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.io.IOException; import java.io.IOException;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.io.IOException; import java.io.IOException;
@@ -60,7 +65,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
LOG.info("Renaming {} to {}", path, newPath.getFileName()); LOG.info("Renaming {} to {}", path, newPath.getFileName());
Files.move(path, path.resolveSibling(newVaultName)); Files.move(path, path.resolveSibling(newVaultName));
Platform.runLater(() -> { Platform.runLater(() -> {
vault.getVaultSettings().setPath(newPath); vault.getVaultSettings().path().set(newPath);
settings.save(); settings.save();
}); });
} catch (IOException e) { } catch (IOException e) {
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.io.IOException; import java.io.IOException;
@@ -114,7 +114,7 @@ public class Vault {
if (!server.isRunning()) { if (!server.isRunning()) {
server.start(); server.start();
} }
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.getMountName()); servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
servlet.start(); servlet.start();
unlockSuccess = true; unlockSuccess = true;
@@ -167,17 +167,16 @@ public class Vault {
} }
public String getWebDavUrl() { public String getWebDavUrl() {
// TODO implement return servlet.getServletRootUri().toString();
return "http://localhost/not/implemented";
} }
public Path getPath() { public Path getPath() {
return vaultSettings.pathProperty().getValue(); return vaultSettings.path().getValue();
} }
public Binding<String> displayablePath() { public Binding<String> displayablePath() {
Path homeDir = Paths.get(SystemUtils.USER_HOME); Path homeDir = Paths.get(SystemUtils.USER_HOME);
return EasyBind.map(vaultSettings.pathProperty(), p -> { return EasyBind.map(vaultSettings.path(), p -> {
if (p.startsWith(homeDir)) { if (p.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(p); Path relativePath = homeDir.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/"; String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
@@ -192,7 +191,7 @@ public class Vault {
* @return Directory name without preceeding path components and file extension * @return Directory name without preceeding path components and file extension
*/ */
public Binding<String> name() { public Binding<String> name() {
return EasyBind.map(vaultSettings.pathProperty(), Path::getFileName).map(Path::toString); return EasyBind.map(vaultSettings.path(), Path::getFileName).map(Path::toString);
} }
public boolean doesVaultDirectoryExist() { public boolean doesVaultDirectoryExist() {
@@ -238,30 +237,30 @@ public class Vault {
} }
public String getMountName() { public String getMountName() {
return vaultSettings.getMountName(); return vaultSettings.mountName().get();
} }
public void setMountName(String mountName) throws IllegalArgumentException { public void setMountName(String mountName) throws IllegalArgumentException {
if (StringUtils.isBlank(mountName)) { if (StringUtils.isBlank(mountName)) {
throw new IllegalArgumentException("mount name is empty"); throw new IllegalArgumentException("mount name is empty");
} else { } else {
vaultSettings.setMountName(mountName); vaultSettings.mountName().set(VaultSettings.normalizeMountName(mountName));
} }
} }
public Character getWinDriveLetter() { public Character getWinDriveLetter() {
if (vaultSettings.getWinDriveLetter() == null) { if (vaultSettings.winDriveLetter().get() == null) {
return null; return null;
} else { } else {
return vaultSettings.getWinDriveLetter().charAt(0); return vaultSettings.winDriveLetter().get().charAt(0);
} }
} }
public void setWinDriveLetter(Character winDriveLetter) { public void setWinDriveLetter(Character winDriveLetter) {
if (winDriveLetter == null) { if (winDriveLetter == null) {
vaultSettings.setWinDriveLetter(null); vaultSettings.winDriveLetter().set(null);
} else { } else {
vaultSettings.setWinDriveLetter(String.valueOf(winDriveLetter)); vaultSettings.winDriveLetter().set(String.valueOf(winDriveLetter));
} }
} }
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import org.cryptomator.ui.model.VaultModule.PerVault; import org.cryptomator.ui.model.VaultModule.PerVault;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.util.ArrayList; import java.util.ArrayList;
@@ -9,7 +14,6 @@ import javax.inject.Singleton;
import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.VaultSettings; import org.cryptomator.ui.settings.VaultSettings;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change; import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList; import javafx.collections.transformation.TransformationList;
@@ -22,14 +26,9 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
@Inject @Inject
public VaultList(Settings settings, VaultFactory vaultFactory) { public VaultList(Settings settings, VaultFactory vaultFactory) {
this(FXCollections.observableList(settings.getDirectories()), settings, vaultFactory); super(settings.getDirectories());
} this.source = settings.getDirectories();
private VaultList(ObservableList<VaultSettings> source, Settings settings, VaultFactory vaultFactory) {
super(source);
this.source = source;
this.vaultFactory = vaultFactory; this.vaultFactory = vaultFactory;
addListener((Change<? extends Vault> change) -> settings.save());
} }
@Override @Override
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model; package org.cryptomator.ui.model;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings; package org.cryptomator.ui.settings;
import java.io.IOException; import java.io.IOException;
@@ -8,58 +8,55 @@
******************************************************************************/ ******************************************************************************/
package org.cryptomator.ui.settings; package org.cryptomator.ui.settings;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.google.gson.annotations.Expose; import javafx.beans.property.BooleanProperty;
import com.google.gson.annotations.SerializedName; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
public class Settings { public class Settings {
public static final int MIN_PORT = 1024; public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535; public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_CHECK_FOR_UDPATES = true;
public static final int DEFAULT_PORT = 42427; public static final int DEFAULT_PORT = 42427;
public static final boolean DEFAULT_USE_IPV6 = false; public static final boolean DEFAULT_USE_IPV6 = false;
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final String DEFAULT_GVFS_SCHEME = "dav"; public static final String DEFAULT_GVFS_SCHEME = "dav";
public static final boolean DEFAULT_DEBUG_MODE = false; public static final boolean DEFAULT_DEBUG_MODE = false;
private final Consumer<Settings> saveCmd; private final Consumer<Settings> saveCmd;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList();
@Expose private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
@SerializedName("directories") private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private List<VaultSettings> directories; private final BooleanProperty useIpv6 = new SimpleBooleanProperty(DEFAULT_USE_IPV6);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
@Expose private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
@SerializedName("checkForUpdatesEnabled") private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private Boolean checkForUpdatesEnabled;
@Expose
@SerializedName("port")
private Integer port;
@Expose
@SerializedName("useIpv6")
private Boolean useIpv6;
@Expose
@SerializedName("numTrayNotifications")
private Integer numTrayNotifications;
@Expose
@SerializedName("preferredGvfsScheme")
private String preferredGvfsScheme;
@Expose
@SerializedName("debugMode")
private Boolean debugMode;
/** /**
* Package-private constructor; use {@link SettingsProvider}. * Package-private constructor; use {@link SettingsProvider}.
*/ */
Settings(Consumer<Settings> saveCmd) { Settings(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd; this.saveCmd = saveCmd;
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
checkForUpdates.addListener(this::somethingChanged);
port.addListener(this::somethingChanged);
useIpv6.addListener(this::somethingChanged);
numTrayNotifications.addListener(this::somethingChanged);
preferredGvfsScheme.addListener(this::somethingChanged);
debugMode.addListener(this::somethingChanged);
}
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
this.save();
} }
public void save() { public void save() {
@@ -70,75 +67,32 @@ public class Settings {
/* Getter/Setter */ /* Getter/Setter */
public List<VaultSettings> getDirectories() { public ObservableList<VaultSettings> getDirectories() {
if (directories == null) {
directories = new ArrayList<>();
}
return directories; return directories;
} }
public void setDirectories(List<VaultSettings> directories) { public BooleanProperty checkForUpdates() {
this.directories = directories; return checkForUpdates;
} }
public boolean isCheckForUpdatesEnabled() { public IntegerProperty port() {
// not false meaning "null or true", so that true is the default value, if not setting exists yet.
return !Boolean.FALSE.equals(checkForUpdatesEnabled);
}
public void setCheckForUpdatesEnabled(boolean checkForUpdatesEnabled) {
this.checkForUpdatesEnabled = checkForUpdatesEnabled;
}
public void setPort(int port) {
if (!isPortValid(port)) {
throw new IllegalArgumentException("Invalid port");
}
this.port = port;
}
public int getPort() {
if (port == null || !isPortValid(port)) {
return DEFAULT_PORT;
} else {
return port; return port;
} }
public BooleanProperty useIpv6() {
return useIpv6;
} }
public boolean isPortValid(int port) { public IntegerProperty numTrayNotifications() {
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT || port == 0; return numTrayNotifications;
} }
public boolean shouldUseIpv6() { public StringProperty preferredGvfsScheme() {
return useIpv6 == null ? DEFAULT_USE_IPV6 : useIpv6; return preferredGvfsScheme;
} }
public void setUseIpv6(boolean useIpv6) { public BooleanProperty debugMode() {
this.useIpv6 = useIpv6; return debugMode;
}
public Integer getNumTrayNotifications() {
return numTrayNotifications == null ? DEFAULT_NUM_TRAY_NOTIFICATIONS : numTrayNotifications;
}
public void setNumTrayNotifications(Integer numTrayNotifications) {
this.numTrayNotifications = numTrayNotifications;
}
public String getPreferredGvfsScheme() {
return preferredGvfsScheme == null ? DEFAULT_GVFS_SCHEME : preferredGvfsScheme;
}
public void setPreferredGvfsScheme(String preferredGvfsScheme) {
this.preferredGvfsScheme = preferredGvfsScheme;
}
public boolean getDebugMode() {
return debugMode == null ? DEFAULT_DEBUG_MODE : debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
} }
} }
@@ -1,21 +0,0 @@
package org.cryptomator.ui.settings;
import java.lang.reflect.Type;
import java.util.function.Consumer;
import com.google.gson.InstanceCreator;
class SettingsInstanceCreator implements InstanceCreator<Settings> {
private final Consumer<Settings> saveCmd;
public SettingsInstanceCreator(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
@Override
public Settings createInstance(Type type) {
return new Settings(saveCmd);
}
}
@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
private final Consumer<Settings> saveCmd;
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
public SettingsJsonAdapter(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
@Override
public void write(JsonWriter out, Settings value) throws IOException {
out.beginObject();
out.name("directories");
writeVaultSettingsArray(out, value.getDirectories());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("port").value(value.port().get());
out.name("useIpv6").value(value.useIpv6().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
out.name("debugMode").value(value.debugMode().get());
out.endObject();
}
private void writeVaultSettingsArray(JsonWriter out, Iterable<VaultSettings> vaultSettings) throws IOException {
out.beginArray();
for (VaultSettings value : vaultSettings) {
vaultSettingsJsonAdapter.write(out, value);
}
out.endArray();
}
@Override
public Settings read(JsonReader in) throws IOException {
Settings settings = new Settings(saveCmd);
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in));
break;
case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean());
break;
case "port":
settings.port().set(in.nextInt());
break;
case "useIpv6":
settings.useIpv6().set(in.nextBoolean());
break;
case "numTrayNotifications":
settings.numTrayNotifications().set(in.nextInt());
break;
case "preferredGvfsScheme":
settings.preferredGvfsScheme().set(in.nextString());
break;
case "debugMode":
settings.debugMode().set(in.nextBoolean());
break;
default:
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
}
}
in.endObject();
return settings;
}
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
List<VaultSettings> result = new ArrayList<>();
in.beginArray();
while (!JsonToken.END_ARRAY.equals(in.peek())) {
result.add(vaultSettingsJsonAdapter.read(in));
}
in.endArray();
return result;
}
}
@@ -21,7 +21,6 @@ import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -63,15 +62,15 @@ public class SettingsProvider implements Provider<Settings> {
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>(); private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final AtomicReference<Settings> settings = new AtomicReference<>(); private final AtomicReference<Settings> settings = new AtomicReference<>();
private final SettingsInstanceCreator settingsInstanceCreator = new SettingsInstanceCreator(this::scheduleSave); private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter(this::scheduleSave);
private final Gson gson; private final Gson gson;
@Inject @Inject
public SettingsProvider() { public SettingsProvider() {
GsonBuilder gsonBuilder = new GsonBuilder() // this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping().excludeFieldsWithoutExposeAnnotation(); .setPrettyPrinting().setLenient().disableHtmlEscaping() //
gsonBuilder.registerTypeAdapter(Settings.class, settingsInstanceCreator); .registerTypeAdapter(Settings.class, settingsJsonAdapter) //
this.gson = gsonBuilder.create(); .create();
} }
private Path getSettingsPath() { private Path getSettingsPath() {
@@ -101,7 +100,7 @@ public class SettingsProvider implements Provider<Settings> {
LOG.info("Settings loaded from " + settingsPath); LOG.info("Settings loaded from " + settingsPath);
} catch (IOException e) { } catch (IOException e) {
LOG.info("Failed to load settings, creating new one."); LOG.info("Failed to load settings, creating new one.");
settings = settingsInstanceCreator.createInstance(Settings.class); settings = new Settings(this::scheduleSave);
} }
return settings; return settings;
} }
@@ -120,7 +119,7 @@ public class SettingsProvider implements Provider<Settings> {
} }
private void save(Settings settings) { private void save(Settings settings) {
Objects.requireNonNull(settings); assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
final Path settingsPath = getSettingsPath(); final Path settingsPath = getSettingsPath();
try { try {
Files.createDirectories(settingsPath.getParent()); Files.createDirectories(settingsPath.getParent());
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings; package org.cryptomator.ui.settings;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -8,23 +13,31 @@ import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.fxmisc.easybind.EasyBind;
import com.google.gson.annotations.JsonAdapter; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@JsonAdapter(VaultSettingsJsonAdapter.class)
public class VaultSettings { public class VaultSettings {
private final String id; private final String id;
private final Property<Path> path = new SimpleObjectProperty<>(); private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final Property<String> mountName = new SimpleStringProperty(); private final StringProperty mountName = new SimpleStringProperty();
private final Property<String> winDriveLetter = new SimpleStringProperty(); private final StringProperty winDriveLetter = new SimpleStringProperty();
public VaultSettings(String id) { public VaultSettings(String id) {
this.id = Objects.requireNonNull(id); this.id = Objects.requireNonNull(id);
EasyBind.subscribe(path, this::deriveMountNameFromPath);
// TODO: automatically save settings, when chaning vaultSettings
}
private void deriveMountNameFromPath(Path path) {
if (path != null && StringUtils.isBlank(mountName.get())) {
mountName.set(normalizeMountName(path.getFileName().toString()));
}
} }
public static VaultSettings withRandomId() { public static VaultSettings withRandomId() {
@@ -48,10 +61,7 @@ public class VaultSettings {
return uuidBuffer.array(); return uuidBuffer.array();
} }
/* public static String normalizeMountName(String mountName) {
* visible for testing
*/
static String normalizeMountName(String mountName) {
String normalizedMountName = StringUtils.stripAccents(mountName); String normalizedMountName = StringUtils.stripAccents(mountName);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (char c : normalizedMountName.toCharArray()) { for (char c : normalizedMountName.toCharArray()) {
@@ -76,43 +86,16 @@ public class VaultSettings {
return id; return id;
} }
public Property<Path> pathProperty() { public ObjectProperty<Path> path() {
return path; return path;
} }
public Path getPath() { public StringProperty mountName() {
return path.getValue();
}
public void setPath(Path path) {
this.path.setValue(path);
if (StringUtils.isBlank(getMountName())) {
setMountName(path.getFileName().toString());
}
}
public Property<String> mountNameProperty() {
return mountName; return mountName;
} }
public String getMountName() { public StringProperty winDriveLetter() {
return mountName.getValue(); return winDriveLetter;
}
public void setMountName(String mountName) {
this.mountName.setValue(normalizeMountName(mountName));
}
public Property<String> winDriveLetterProperty() {
return mountName;
}
public String getWinDriveLetter() {
return winDriveLetter.getValue();
}
public void setWinDriveLetter(String winDriveLetter) {
this.winDriveLetter.setValue(winDriveLetter);
} }
/* Hashcode/Equals */ /* Hashcode/Equals */
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings; package org.cryptomator.ui.settings;
import java.io.IOException; import java.io.IOException;
@@ -18,9 +23,9 @@ class VaultSettingsJsonAdapter extends TypeAdapter<VaultSettings> {
public void write(JsonWriter out, VaultSettings value) throws IOException { public void write(JsonWriter out, VaultSettings value) throws IOException {
out.beginObject(); out.beginObject();
out.name("id").value(value.getId()); out.name("id").value(value.getId());
out.name("path").value(value.getPath().toString()); out.name("path").value(value.path().get().toString());
out.name("mountName").value(value.getMountName()); out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.getWinDriveLetter()); out.name("winDriveLetter").value(value.winDriveLetter().get());
out.endObject(); out.endObject();
} }
@@ -55,9 +60,9 @@ class VaultSettingsJsonAdapter extends TypeAdapter<VaultSettings> {
in.endObject(); in.endObject();
VaultSettings settings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id); VaultSettings settings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
settings.setMountName(mountName); settings.mountName().set(mountName);
settings.setPath(Paths.get(path)); settings.path().set(Paths.get(path));
settings.setWinDriveLetter(winDriveLetter); settings.winDriveLetter().set(winDriveLetter);
return settings; return settings;
} }
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.util; package org.cryptomator.ui.util;
import java.util.Optional; import java.util.Optional;
@@ -1,3 +1,8 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.util; package org.cryptomator.ui.util;
import java.util.ArrayList; import java.util.ArrayList;
@@ -16,6 +16,8 @@
<?import javafx.scene.control.TextField?> <?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ChoiceBox?> <?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true"> <VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" /> <Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
@@ -36,7 +38,11 @@
<!-- Row 1 --> <!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.port.label" cacheShape="true" cache="true" /> <Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.port.label" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" /> <HBox GridPane.rowIndex="1" GridPane.columnIndex="1" spacing="6.0">
<TextField fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" />
<Button text="%settings.port.apply" fx:id="changePortButton" onAction="#changePort"/>
</HBox>
<!-- Row 2 --> <!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" /> <Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />
@@ -98,8 +98,9 @@ unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
# settings.fxml # settings.fxml
settings.version.label=Version %s settings.version.label=Version %s
settings.checkForUpdates.label=Check for Updates settings.checkForUpdates.label=Check for Updates
settings.port.label=WebDAV Port * settings.port.label=WebDAV Port
settings.port.prompt=0 = Choose automatically settings.port.prompt=0 = Choose automatically
settings.port.apply=Apply
settings.useipv6.label=Use IPv6 Literal settings.useipv6.label=Use IPv6 Literal
settings.prefGvfsScheme.label=WebDAV Scheme settings.prefGvfsScheme.label=WebDAV Scheme
settings.debugMode.label=Debug Mode * settings.debugMode.label=Debug Mode *
@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
public class SettingsJsonAdapterTest {
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(this::noop);
private void noop(Settings settings) {
}
@Test
public void testDeserialize() throws IOException {
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
+ "\"checkForUpdatesEnabled\": true,"//
+ "\"port\": 8080,"//
+ "\"useIpv6\": true,"//
+ "\"numTrayNotifications\": 42}";
Settings settings = adapter.fromJson(json);
Assert.assertTrue(settings.checkForUpdates().get());
Assert.assertEquals(2, settings.getDirectories().size());
Assert.assertEquals(8080, settings.port().get());
Assert.assertTrue(settings.useIpv6().get());
Assert.assertEquals(42, settings.numTrayNotifications().get());
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
}
}
@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.settings;
import java.io.IOException;
import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
public class VaultSettingsJsonAdapterTest {
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
@Test
public void testDeserialize() throws IOException {
VaultSettings settings = adapter.fromJson("{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}");
Assert.assertEquals("foo", settings.getId());
Assert.assertEquals(Paths.get("/foo/bar"), settings.path().get());
Assert.assertEquals("test", settings.mountName().get());
Assert.assertEquals("X", settings.winDriveLetter().get());
}
}