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

View File

@@ -8,6 +8,7 @@
*******************************************************************************/
package org.cryptomator.ui;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
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.SettingsProvider;
import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dagger.Module;
import dagger.Provides;
import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.stage.Stage;
@Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class})
@@ -83,8 +86,20 @@ class CryptomatorModule {
@Provides
@Singleton
WebDavServer provideWebDavServer(Settings settings) {
return WebDavServer.create("localhost", settings.getPort());
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
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;
}
}

View File

@@ -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;
import static java.util.Arrays.asList;
@@ -36,7 +41,7 @@ public class DebugMode {
}
public void initialize() {
if (settings.getDebugMode()) {
if (settings.debugMode().get()) {
enable();
LOG.debug("Debug mode initialized");
}

View File

@@ -147,11 +147,11 @@ class ExitUtil {
}
private void showTrayNotification(TrayIcon trayIcon) {
if (settings.getNumTrayNotifications() <= 0) {
int remainingTrayNotification = settings.numTrayNotifications().get();
if (remainingTrayNotification <= 0) {
return;
} else {
settings.setNumTrayNotifications(settings.getNumTrayNotifications() - 1);
settings.save();
settings.numTrayNotifications().set(remainingTrayNotification - 1);
}
final Runnable notificationCmd;
if (SystemUtils.IS_OS_MAC_OSX) {

View File

@@ -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;
import org.cryptomator.ui.settings.Localization;

View File

@@ -232,7 +232,7 @@ public class MainController extends LocalizedFXMLViewController {
}
final VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.setPath(vaultPath);
vaultSettings.path().set(vaultPath);
final Vault vault = vaultFactoy.get(vaultSettings);
if (!vaults.contains(vault)) {
vaults.add(vault);

View File

@@ -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;
import java.net.URL;

View File

@@ -18,9 +18,11 @@ import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.settings.Localization;
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.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
@@ -44,6 +46,9 @@ public class SettingsController extends LocalizedFXMLViewController {
@FXML
private TextField portField;
@FXML
private Button changePortButton;
@FXML
private Label useIpv6Label;
@@ -65,25 +70,26 @@ public class SettingsController extends LocalizedFXMLViewController {
@Override
public void initialize() {
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally());
portField.setText(String.valueOf(settings.getPort()));
checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
portField.setText(String.valueOf(settings.port().intValue()));
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
useIpv6Label.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")));
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.getItems().add("dav");
prefGvfsScheme.getItems().add("webdav");
prefGvfsScheme.setValue(settings.getPreferredGvfsScheme());
debugModeCheckbox.setSelected(settings.getDebugMode());
prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
debugModeCheckbox.setSelected(settings.debugMode().get());
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange);
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange);
EasyBind.subscribe(prefGvfsScheme.valueProperty(), this::prefGvfsSchemeDidChange);
EasyBind.subscribe(debugModeCheckbox.selectedProperty(), this::debugModeDidChange);
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
settings.useIpv6().bind(useIpv6Checkbox.selectedProperty());
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
settings.debugMode().bind(debugModeCheckbox.selectedProperty());
}
@Override
@@ -95,38 +101,28 @@ public class SettingsController extends LocalizedFXMLViewController {
return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
}
private void checkForUpdateDidChange(Boolean newValue) {
settings.setCheckForUpdatesEnabled(newValue);
settings.save();
}
private void portDidChange(String newValue) {
@FXML
private void changePort(ActionEvent evt) {
assert isPortValid() : "Button must be disabled, if port is invalid.";
try {
int port = Integer.parseInt(newValue);
if (!settings.isPortValid(port)) {
settings.setPort(Settings.DEFAULT_PORT);
} else {
settings.setPort(port);
settings.save();
}
int port = Integer.parseInt(portField.getText());
settings.port().set(port);
} catch (NumberFormatException e) {
portField.setText(String.valueOf(Settings.DEFAULT_PORT));
throw new IllegalStateException("Button must be disabled, if port is invalid.", e);
}
}
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 boolean isPortValid() {
try {
int port = Integer.parseInt(portField.getText());
if (port == 0 || port >= Settings.MIN_PORT && port <= Settings.MAX_PORT) {
return true;
} else {
return false;
}
} catch (NumberFormatException e) {
return false;
}
}
private void filterNumericKeyEvents(KeyEvent t) {

View File

@@ -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;
import java.net.URL;

View File

@@ -82,7 +82,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
public void initialize() {
if (areUpdatesManagedExternally()) {
checkForUpdatesContainer.setVisible(false);
} else if (settings.isCheckForUpdatesEnabled()) {
} else if (settings.checkForUpdates().get()) {
this.checkForUpdates();
}
}

View File

@@ -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;
import java.util.Arrays;

View File

@@ -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;
import java.io.IOException;

View File

@@ -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;
import java.io.IOException;
@@ -60,7 +65,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
LOG.info("Renaming {} to {}", path, newPath.getFileName());
Files.move(path, path.resolveSibling(newVaultName));
Platform.runLater(() -> {
vault.getVaultSettings().setPath(newPath);
vault.getVaultSettings().path().set(newPath);
settings.save();
});
} catch (IOException e) {

View File

@@ -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;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@@ -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;
import java.io.IOException;

View File

@@ -114,7 +114,7 @@ public class Vault {
if (!server.isRunning()) {
server.start();
}
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.getMountName());
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
servlet.start();
unlockSuccess = true;
@@ -167,17 +167,16 @@ public class Vault {
}
public String getWebDavUrl() {
// TODO implement
return "http://localhost/not/implemented";
return servlet.getServletRootUri().toString();
}
public Path getPath() {
return vaultSettings.pathProperty().getValue();
return vaultSettings.path().getValue();
}
public Binding<String> displayablePath() {
Path homeDir = Paths.get(SystemUtils.USER_HOME);
return EasyBind.map(vaultSettings.pathProperty(), p -> {
return EasyBind.map(vaultSettings.path(), p -> {
if (p.startsWith(homeDir)) {
Path relativePath = homeDir.relativize(p);
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
@@ -192,7 +191,7 @@ public class Vault {
* @return Directory name without preceeding path components and file extension
*/
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() {
@@ -238,30 +237,30 @@ public class Vault {
}
public String getMountName() {
return vaultSettings.getMountName();
return vaultSettings.mountName().get();
}
public void setMountName(String mountName) throws IllegalArgumentException {
if (StringUtils.isBlank(mountName)) {
throw new IllegalArgumentException("mount name is empty");
} else {
vaultSettings.setMountName(mountName);
vaultSettings.mountName().set(VaultSettings.normalizeMountName(mountName));
}
}
public Character getWinDriveLetter() {
if (vaultSettings.getWinDriveLetter() == null) {
if (vaultSettings.winDriveLetter().get() == null) {
return null;
} else {
return vaultSettings.getWinDriveLetter().charAt(0);
return vaultSettings.winDriveLetter().get().charAt(0);
}
}
public void setWinDriveLetter(Character winDriveLetter) {
if (winDriveLetter == null) {
vaultSettings.setWinDriveLetter(null);
vaultSettings.winDriveLetter().set(null);
} else {
vaultSettings.setWinDriveLetter(String.valueOf(winDriveLetter));
vaultSettings.winDriveLetter().set(String.valueOf(winDriveLetter));
}
}

View File

@@ -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;
import org.cryptomator.ui.model.VaultModule.PerVault;

View File

@@ -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;
import java.util.ArrayList;
@@ -9,7 +14,6 @@ import javax.inject.Singleton;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.VaultSettings;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
@@ -22,14 +26,9 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
@Inject
public VaultList(Settings settings, VaultFactory vaultFactory) {
this(FXCollections.observableList(settings.getDirectories()), settings, vaultFactory);
}
private VaultList(ObservableList<VaultSettings> source, Settings settings, VaultFactory vaultFactory) {
super(source);
this.source = source;
super(settings.getDirectories());
this.source = settings.getDirectories();
this.vaultFactory = vaultFactory;
addListener((Change<? extends Vault> change) -> settings.save());
}
@Override

View File

@@ -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;
import java.lang.annotation.Documented;

View File

@@ -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;
import java.nio.file.FileSystems;

View File

@@ -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;
import java.io.IOException;

View File

@@ -8,58 +8,55 @@
******************************************************************************/
package org.cryptomator.ui.settings;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.BooleanProperty;
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 static final int MIN_PORT = 1024;
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 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 boolean DEFAULT_DEBUG_MODE = false;
private final Consumer<Settings> saveCmd;
@Expose
@SerializedName("directories")
private List<VaultSettings> directories;
@Expose
@SerializedName("checkForUpdatesEnabled")
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;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList();
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final BooleanProperty useIpv6 = new SimpleBooleanProperty(DEFAULT_USE_IPV6);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
/**
* Package-private constructor; use {@link SettingsProvider}.
*/
Settings(Consumer<Settings> 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() {
@@ -70,75 +67,32 @@ public class Settings {
/* Getter/Setter */
public List<VaultSettings> getDirectories() {
if (directories == null) {
directories = new ArrayList<>();
}
public ObservableList<VaultSettings> getDirectories() {
return directories;
}
public void setDirectories(List<VaultSettings> directories) {
this.directories = directories;
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
public boolean isCheckForUpdatesEnabled() {
// not false meaning "null or true", so that true is the default value, if not setting exists yet.
return !Boolean.FALSE.equals(checkForUpdatesEnabled);
public IntegerProperty port() {
return port;
}
public void setCheckForUpdatesEnabled(boolean checkForUpdatesEnabled) {
this.checkForUpdatesEnabled = checkForUpdatesEnabled;
public BooleanProperty useIpv6() {
return useIpv6;
}
public void setPort(int port) {
if (!isPortValid(port)) {
throw new IllegalArgumentException("Invalid port");
}
this.port = port;
public IntegerProperty numTrayNotifications() {
return numTrayNotifications;
}
public int getPort() {
if (port == null || !isPortValid(port)) {
return DEFAULT_PORT;
} else {
return port;
}
public StringProperty preferredGvfsScheme() {
return preferredGvfsScheme;
}
public boolean isPortValid(int port) {
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT || port == 0;
}
public boolean shouldUseIpv6() {
return useIpv6 == null ? DEFAULT_USE_IPV6 : useIpv6;
}
public void setUseIpv6(boolean useIpv6) {
this.useIpv6 = useIpv6;
}
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;
public BooleanProperty debugMode() {
return debugMode;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -21,7 +21,6 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -63,15 +62,15 @@ public class SettingsProvider implements Provider<Settings> {
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = 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;
@Inject
public SettingsProvider() {
GsonBuilder gsonBuilder = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping().excludeFieldsWithoutExposeAnnotation();
gsonBuilder.registerTypeAdapter(Settings.class, settingsInstanceCreator);
this.gson = gsonBuilder.create();
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
}
private Path getSettingsPath() {
@@ -101,7 +100,7 @@ public class SettingsProvider implements Provider<Settings> {
LOG.info("Settings loaded from " + settingsPath);
} catch (IOException e) {
LOG.info("Failed to load settings, creating new one.");
settings = settingsInstanceCreator.createInstance(Settings.class);
settings = new Settings(this::scheduleSave);
}
return settings;
}
@@ -120,7 +119,7 @@ public class SettingsProvider implements Provider<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();
try {
Files.createDirectories(settingsPath.getParent());

View File

@@ -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;
import java.nio.ByteBuffer;
@@ -8,23 +13,31 @@ import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.fxmisc.easybind.EasyBind;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.property.Property;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@JsonAdapter(VaultSettingsJsonAdapter.class)
public class VaultSettings {
private final String id;
private final Property<Path> path = new SimpleObjectProperty<>();
private final Property<String> mountName = new SimpleStringProperty();
private final Property<String> winDriveLetter = new SimpleStringProperty();
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty mountName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
public VaultSettings(String 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() {
@@ -48,10 +61,7 @@ public class VaultSettings {
return uuidBuffer.array();
}
/*
* visible for testing
*/
static String normalizeMountName(String mountName) {
public static String normalizeMountName(String mountName) {
String normalizedMountName = StringUtils.stripAccents(mountName);
StringBuilder builder = new StringBuilder();
for (char c : normalizedMountName.toCharArray()) {
@@ -76,43 +86,16 @@ public class VaultSettings {
return id;
}
public Property<Path> pathProperty() {
public ObjectProperty<Path> path() {
return path;
}
public Path getPath() {
return path.getValue();
}
public void setPath(Path path) {
this.path.setValue(path);
if (StringUtils.isBlank(getMountName())) {
setMountName(path.getFileName().toString());
}
}
public Property<String> mountNameProperty() {
public StringProperty mountName() {
return mountName;
}
public String getMountName() {
return mountName.getValue();
}
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);
public StringProperty winDriveLetter() {
return winDriveLetter;
}
/* Hashcode/Equals */

View File

@@ -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;
import java.io.IOException;
@@ -18,9 +23,9 @@ class VaultSettingsJsonAdapter extends TypeAdapter<VaultSettings> {
public void write(JsonWriter out, VaultSettings value) throws IOException {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.getPath().toString());
out.name("mountName").value(value.getMountName());
out.name("winDriveLetter").value(value.getWinDriveLetter());
out.name("path").value(value.path().get().toString());
out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.endObject();
}
@@ -55,9 +60,9 @@ class VaultSettingsJsonAdapter extends TypeAdapter<VaultSettings> {
in.endObject();
VaultSettings settings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
settings.setMountName(mountName);
settings.setPath(Paths.get(path));
settings.setWinDriveLetter(winDriveLetter);
settings.mountName().set(mountName);
settings.path().set(Paths.get(path));
settings.winDriveLetter().set(winDriveLetter);
return settings;
}

View File

@@ -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;
import java.util.Optional;

View File

@@ -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;
import java.util.ArrayList;

View File

@@ -16,6 +16,8 @@
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<VBox 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" />
@@ -36,7 +38,11 @@
<!-- Row 1 -->
<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 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />

View File

@@ -98,8 +98,9 @@ unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
# settings.fxml
settings.version.label=Version %s
settings.checkForUpdates.label=Check for Updates
settings.port.label=WebDAV Port *
settings.port.label=WebDAV Port
settings.port.prompt=0 = Choose automatically
settings.port.apply=Apply
settings.useipv6.label=Use IPv6 Literal
settings.prefGvfsScheme.label=WebDAV Scheme
settings.debugMode.label=Debug Mode *

View File

@@ -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());
}
}

View File

@@ -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());
}
}