replace GSON from settings

This commit is contained in:
Sebastian Stenzel
2023-06-29 22:41:19 +02:00
parent 3c53968dd2
commit b1ff94bdd6
15 changed files with 478 additions and 560 deletions

View File

@@ -10,6 +10,8 @@ package org.cryptomator.common.settings;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
@@ -27,59 +29,85 @@ import java.util.function.Consumer;
public class Settings {
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
public static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
public static final boolean DEFAULT_START_HIDDEN = false;
public static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
public static final boolean DEFAULT_USE_KEYCHAIN = true;
public static final int DEFAULT_PORT = 42427;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final boolean DEFAULT_DEBUG_MODE = false;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
static final boolean DEFAULT_START_HIDDEN = false;
static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
static final boolean DEFAULT_USE_KEYCHAIN = true;
static final int DEFAULT_PORT = 42427;
static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
static final boolean DEFAULT_DEBUG_MODE = false;
static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
@Deprecated // to be changed to "whatever is available" eventually
public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
public static final String DEFAULT_LICENSE_KEY = "";
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
public static final String DEFAULT_DISPLAY_CONFIGURATION = "";
public static final String DEFAULT_LANGUAGE = null;
static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UPDATES);
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
private final BooleanProperty autoCloseVaults = new SimpleBooleanProperty(DEFAULT_AUTO_CLOSE_VAULTS);
private final BooleanProperty useKeychain = new SimpleBooleanProperty(DEFAULT_USE_KEYCHAIN);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
private final ObjectProperty<String> keychainProvider = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_PROVIDER);
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
private final BooleanProperty showMinimizeButton = new SimpleBooleanProperty(DEFAULT_SHOW_MINIMIZE_BUTTON);
private final ObservableList<VaultSettings> directories;
private final BooleanProperty askedForUpdateCheck;
private final BooleanProperty checkForUpdates;
private final BooleanProperty startHidden;
private final BooleanProperty autoCloseVaults;
private final BooleanProperty useKeychain;
private final IntegerProperty port;
private final IntegerProperty numTrayNotifications;
private final BooleanProperty debugMode;
private final ObjectProperty<UiTheme> theme;
private final StringProperty keychainProvider;
private final ObjectProperty<NodeOrientation> userInterfaceOrientation;
private final StringProperty licenseKey;
private final BooleanProperty showMinimizeButton;
private final BooleanProperty showTrayIcon;
private final IntegerProperty windowXPosition = new SimpleIntegerProperty();
private final IntegerProperty windowYPosition = new SimpleIntegerProperty();
private final IntegerProperty windowWidth = new SimpleIntegerProperty();
private final IntegerProperty windowHeight = new SimpleIntegerProperty();
private final ObjectProperty<String> displayConfiguration = new SimpleObjectProperty<>(DEFAULT_DISPLAY_CONFIGURATION);
private final StringProperty language = new SimpleStringProperty(DEFAULT_LANGUAGE);
private final StringProperty mountService = new SimpleStringProperty();
private final IntegerProperty windowXPosition;
private final IntegerProperty windowYPosition;
private final IntegerProperty windowWidth;
private final IntegerProperty windowHeight;
private final StringProperty displayConfiguration;
private final StringProperty language;
private final StringProperty mountService;
private Consumer<Settings> saveCmd;
public static Settings create(Environment env) {
var defaults = new SettingsJson();
defaults.showTrayIcon = env.showTrayIcon();
return new Settings(defaults);
}
/**
* Package-private constructor; use {@link SettingsProvider}.
* Recreate settings from json
*
* @param json The parsed settings.json
*/
Settings(Environment env) {
this.showTrayIcon = new SimpleBooleanProperty(env.showTrayIcon());
Settings(SettingsJson json) {
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck);
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain);
this.port = new SimpleIntegerProperty(this, "webDavPort", json.port);
this.numTrayNotifications = new SimpleIntegerProperty(this, "numTrayNotifications", json.numTrayNotifications);
this.debugMode = new SimpleBooleanProperty(this, "debugMode", json.debugMode);
this.theme = new SimpleObjectProperty<>(this, "theme", json.theme);
this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider);
this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT));
this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey);
this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton);
this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon);
this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition);
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
this.displayConfiguration = new SimpleStringProperty(this, "displayConfiguration", json.displayConfiguration);
this.language = new SimpleStringProperty(this, "language", json.language);
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
migrateLegacySettings(json);
directories.addListener(this::somethingChanged);
askedForUpdateCheck.addListener(this::somethingChanged);
@@ -105,6 +133,72 @@ public class Settings {
mountService.addListener(this::somethingChanged);
}
@SuppressWarnings("deprecation")
private void migrateLegacySettings(SettingsJson json) {
// implicit migration of 1.6.x legacy setting "preferredVolumeImpl":
if (this.mountService.get() == null && json.preferredVolumeImpl != null) {
this.mountService.set(switch (json.preferredVolumeImpl) {
case "Dokany" -> "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
case "FUSE" -> {
if (SystemUtils.IS_OS_WINDOWS) {
yield "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
} else if (SystemUtils.IS_OS_MAC) {
yield "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
} else {
yield "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
}
}
default -> {
if (SystemUtils.IS_OS_WINDOWS) {
yield "org.cryptomator.frontend.webdav.mount.WindowsMounter";
} else if (SystemUtils.IS_OS_MAC) {
yield "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
} else {
yield "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
}
}
});
}
}
public SettingsJson serialized() {
var json = new SettingsJson();
json.directories = directories.stream().map(VaultSettings::serialized).toList();
json.askedForUpdateCheck = askedForUpdateCheck.get();
json.checkForUpdatesEnabled = checkForUpdates.get();
json.startHidden = startHidden.get();
json.autoCloseVaults = autoCloseVaults.get();
json.useKeychain = useKeychain.get();
json.port = port.get();
json.numTrayNotifications = numTrayNotifications.get();
json.debugMode = debugMode.get();
json.theme = theme.get();
json.keychainProvider = keychainProvider.get();
json.uiOrientation = userInterfaceOrientation.get().name();
json.licenseKey = licenseKey.get();
json.showMinimizeButton = showMinimizeButton.get();
json.showTrayIcon = showTrayIcon.get();
json.windowXPosition = windowXPosition.get();
json.windowYPosition = windowYPosition.get();
json.windowWidth = windowWidth.get();
json.windowHeight = windowHeight.get();
json.displayConfiguration = displayConfiguration.get();
json.language = language.get();
json.mountService = mountService.get();
return json;
}
private <E extends Enum<E>> E parseEnum(String value, Class<E> clazz, E defaultValue) {
try {
return Enum.valueOf(clazz, value.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("No value {}.{}. Defaulting to {}.", clazz.getSimpleName(), value, defaultValue);
return defaultValue;
}
}
// TODO rename to setChangeListener
void setSaveCmd(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
@@ -120,6 +214,7 @@ public class Settings {
}
/* Getter/Setter */
// TODO: remove accessors, make fields public
public ObservableList<VaultSettings> getDirectories() {
return directories;
@@ -141,7 +236,7 @@ public class Settings {
return autoCloseVaults;
}
public BooleanProperty useKeychain() { return useKeychain; }
public BooleanProperty useKeychain() {return useKeychain;}
public IntegerProperty port() {
return port;
@@ -163,7 +258,7 @@ public class Settings {
return theme;
}
public ObjectProperty<String> keychainProvider() {return keychainProvider;}
public StringProperty keychainProvider() {return keychainProvider;}
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
return userInterfaceOrientation;
@@ -197,7 +292,7 @@ public class Settings {
return windowHeight;
}
public ObjectProperty<String> displayConfigurationProperty() {
public StringProperty displayConfigurationProperty() {
return displayConfiguration;
}

View File

@@ -0,0 +1,86 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class SettingsJson {
@JsonProperty("directories")
List<VaultSettingsJson> directories = List.of();
@JsonProperty("writtenByVersion")
String writtenByVersion;
@JsonProperty("askedForUpdateCheck")
boolean askedForUpdateCheck = Settings.DEFAULT_ASKED_FOR_UPDATE_CHECK;
@JsonProperty("autoCloseVaults")
boolean autoCloseVaults = Settings.DEFAULT_AUTO_CLOSE_VAULTS;
@JsonProperty("checkForUpdatesEnabled")
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
@JsonProperty("debugMode")
boolean debugMode = Settings.DEFAULT_DEBUG_MODE;
@JsonProperty("theme")
UiTheme theme = Settings.DEFAULT_THEME;
@JsonProperty("displayConfiguration")
String displayConfiguration;
@JsonProperty("keychainProvider")
String keychainProvider = Settings.DEFAULT_KEYCHAIN_PROVIDER;
@JsonProperty("language")
String language;
@JsonProperty("licenseKey")
String licenseKey;
@JsonProperty("mountService")
String mountService;
@JsonProperty("numTrayNotifications")
int numTrayNotifications = Settings.DEFAULT_NUM_TRAY_NOTIFICATIONS;
@JsonProperty("port")
int port = Settings.DEFAULT_PORT;
@JsonProperty("showMinimizeButton")
boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON;
@JsonProperty("showTrayIcon")
boolean showTrayIcon;
@JsonProperty("startHidden")
boolean startHidden = Settings.DEFAULT_START_HIDDEN;
@JsonProperty("uiOrientation")
String uiOrientation = Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
@JsonProperty("useKeychain")
boolean useKeychain = Settings.DEFAULT_USE_KEYCHAIN;
@JsonProperty("windowHeight")
int windowHeight;
@JsonProperty("windowWidth")
int windowWidth;
@JsonProperty("windowXPosition")
int windowXPosition;
@JsonProperty("windowYPosition")
int windowYPosition;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String preferredVolumeImpl;
}

View File

@@ -1,183 +0,0 @@
/*******************************************************************************
* 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.common.settings;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.geometry.NodeOrientation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
private final Environment env;
@Inject
public SettingsJsonAdapter(Environment env) {
this.env = env;
}
@Override
public void write(JsonWriter out, Settings value) throws IOException {
out.beginObject();
out.name("writtenByVersion").value(env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse(""));
out.name("directories");
writeVaultSettingsArray(out, value.getDirectories());
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
out.name("autoCloseVaults").value(value.autoCloseVaults().get());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("debugMode").value(value.debugMode().get());
out.name("displayConfiguration").value((value.displayConfigurationProperty().get()));
out.name("keychainProvider").value(value.keychainProvider().get());
out.name("language").value((value.languageProperty().get()));
out.name("licenseKey").value(value.licenseKey().get());
out.name("mountService").value(value.mountService().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("port").value(value.port().get());
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
out.name("showTrayIcon").value(value.showTrayIcon().get());
out.name("startHidden").value(value.startHidden().get());
out.name("theme").value(value.theme().get().name());
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
out.name("useKeychain").value(value.useKeychain().get());
out.name("windowHeight").value((value.windowHeightProperty().get()));
out.name("windowWidth").value((value.windowWidthProperty().get()));
out.name("windowXPosition").value((value.windowXPositionProperty().get()));
out.name("windowYPosition").value((value.windowYPositionProperty().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(env);
//1.6.x legacy
String volumeImpl = null;
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "writtenByVersion" -> in.skipValue(); //noop
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
case "autoCloseVaults" -> settings.autoCloseVaults().set(in.nextBoolean());
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
case "displayConfiguration" -> settings.displayConfigurationProperty().set(in.nextString());
case "keychainProvider" -> settings.keychainProvider().set(in.nextString());
case "language" -> settings.languageProperty().set(in.nextString());
case "licenseKey" -> settings.licenseKey().set(in.nextString());
case "mountService" -> {
var token = in.peek();
if (JsonToken.STRING == token) {
settings.mountService().set(in.nextString());
}
}
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
case "port" -> settings.port().set(in.nextInt());
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
case "useKeychain" -> settings.useKeychain().set(in.nextBoolean());
case "windowHeight" -> settings.windowHeightProperty().set(in.nextInt());
case "windowWidth" -> settings.windowWidthProperty().set(in.nextInt());
case "windowXPosition" -> settings.windowXPositionProperty().set(in.nextInt());
case "windowYPosition" -> settings.windowYPositionProperty().set(in.nextInt());
//1.6.x legacy
case "preferredVolumeImpl" -> volumeImpl = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
}
in.endObject();
//1.6.x legacy
if (volumeImpl != null) {
settings.mountService().set(convertLegacyVolumeImplToMountService(volumeImpl));
}
//legacy end
return settings;
}
private String convertLegacyVolumeImplToMountService(String volumeImpl) {
if (volumeImpl.equals("Dokany")) {
return "org.cryptomator.frontend.dokany.mount.DokanyMountProvider";
} else if (volumeImpl.equals("FUSE")) {
if (SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.fuse.mount.WinFspNetworkMountProvider";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.fuse.mount.MacFuseMountProvider";
} else {
return "org.cryptomator.frontend.fuse.mount.LinuxFuseMountProvider";
}
} else {
if (SystemUtils.IS_OS_WINDOWS) {
return "org.cryptomator.frontend.webdav.mount.WindowsMounter";
} else if (SystemUtils.IS_OS_MAC) {
return "org.cryptomator.frontend.webdav.mount.MacAppleScriptMounter";
} else {
return "org.cryptomator.frontend.webdav.mount.LinuxGioMounter";
}
}
}
private UiTheme parseUiTheme(String uiThemeName) {
try {
return UiTheme.valueOf(uiThemeName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME);
return Settings.DEFAULT_THEME;
}
}
private NodeOrientation parseUiOrientation(String uiOrientationName) {
try {
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION);
return Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
}
}
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

@@ -8,12 +8,10 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Suppliers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,12 +20,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -44,6 +37,7 @@ import java.util.stream.Stream;
@Singleton
public class SettingsProvider implements Supplier<Settings> {
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final long SAVE_DELAY_MS = 1000;
@@ -51,16 +45,16 @@ public class SettingsProvider implements Supplier<Settings> {
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
private final Environment env;
private final ScheduledExecutorService scheduler;
private final Gson gson;
// private final Gson gson;
@Inject
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
this.env = env;
this.scheduler = scheduler;
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
// this.gson = new GsonBuilder() //
// .setPrettyPrinting().setLenient().disableHtmlEscaping() //
// .registerTypeAdapter(Settings.class, settingsJsonAdapter) //
// .create();
}
@Override
@@ -69,28 +63,25 @@ public class SettingsProvider implements Supplier<Settings> {
}
private Settings load() {
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings(env));
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElseGet(() -> Settings.create(env));
settings.setSaveCmd(this::scheduleSave);
return settings;
}
private Stream<Settings> tryLoad(Path path) {
LOG.debug("Attempting to load settings from {}", path);
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
JsonElement json = JsonParser.parseReader(reader);
if (json.isJsonObject()) {
Settings settings = gson.fromJson(json, Settings.class);
LOG.info("Settings loaded from {}", path);
return Stream.of(settings);
} else {
LOG.warn("Invalid json file {}", path);
return Stream.empty();
}
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
var json = JSON.reader().readValue(in, SettingsJson.class);
LOG.info("Settings loaded from {}", path);
var settings = new Settings(json);
return Stream.of(settings);
} catch (JacksonException e) {
LOG.warn("Failed to parse json file {}", path, e);
return Stream.empty();
} catch (NoSuchFileException e) {
return Stream.empty();
} catch (IOException | JsonParseException e) {
LOG.warn("Exception while loading settings from " + path, e);
} catch (IOException e) {
LOG.warn("Failed to load json file {}", path, e);
return Stream.empty();
}
}
@@ -116,9 +107,10 @@ public class SettingsProvider implements Supplier<Settings> {
try {
Files.createDirectories(settingsPath.getParent());
Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp");
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); //
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
gson.toJson(settings, writer);
try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
var jsonObj = settings.serialized();
jsonObj.writtenByVersion = env.getAppVersion() + env.getBuildNumber().map("-"::concat).orElse("");
JSON.writerWithDefaultPrettyPrinter().writeValue(out, jsonObj);
}
Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Settings saved to {}", settingsPath);

View File

@@ -1,9 +1,13 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.SystemUtils;
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum UiTheme {
LIGHT("preferences.interface.theme.light"), //
@JsonEnumDefaultValue @JsonProperty LIGHT("preferences.interface.theme.light"), //
DARK("preferences.interface.theme.dark"), //
AUTOMATIC("preferences.interface.theme.automatic");

View File

@@ -6,7 +6,9 @@
package org.cryptomator.common.settings;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
@@ -20,6 +22,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Random;
@@ -28,33 +31,45 @@ import java.util.Random;
*/
public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
public static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
public static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
static final boolean DEFAULT_REVEAL_AFTER_MOUNT = true;
static final boolean DEFAULT_USES_READONLY_MODE = false;
static final String DEFAULT_MOUNT_FLAGS = ""; // TODO: remove empty default mount flags and let this property be null if not used
static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
private static final Random RNG = new Random();
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty displayName = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REVEAL_AFTER_MOUNT);
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); //TODO: remove empty default mount flags and let this property be null if not used
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
private final ObjectProperty<Path> path;
private final StringProperty displayName;
private final BooleanProperty unlockAfterStartup;
private final BooleanProperty revealAfterMount;
private final BooleanProperty usesReadOnlyMode;
private final StringProperty mountFlags;
private final IntegerProperty maxCleartextFilenameLength;
private final ObjectProperty<WhenUnlocked> actionAfterUnlock;
private final BooleanProperty autoLockWhenIdle;
private final IntegerProperty autoLockIdleSeconds;
private final ObjectProperty<Path> mountPoint;
private final StringExpression mountName;
private final ObjectProperty<Path> mountPoint = new SimpleObjectProperty<>();
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
VaultSettings(VaultSettingsJson json) {
this.id = json.id;
this.path = new SimpleObjectProperty<>(this, "path", json.path == null ? null : Paths.get(json.path));
this.displayName = new SimpleStringProperty(this, "displayName", json.displayName);
this.unlockAfterStartup = new SimpleBooleanProperty(this, "unlockAfterStartup", json.unlockAfterStartup);
this.revealAfterMount = new SimpleBooleanProperty(this, "revealAfterMount", json.revealAfterMount);
this.usesReadOnlyMode = new SimpleBooleanProperty(this, "usesReadOnlyMode", json.usesReadOnlyMode);
this.mountFlags = new SimpleStringProperty(this, "mountFlags", json.mountFlags);
this.maxCleartextFilenameLength = new SimpleIntegerProperty(this, "maxCleartextFilenameLength", json.maxCleartextFilenameLength);
this.actionAfterUnlock = new SimpleObjectProperty<>(this, "actionAfterUnlock", json.actionAfterUnlock);
this.autoLockWhenIdle = new SimpleBooleanProperty(this, "autoLockWhenIdle", json.autoLockWhenIdle);
this.autoLockIdleSeconds = new SimpleIntegerProperty(this, "autoLockIdleSeconds", json.autoLockIdleSeconds);
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
final String name;
if (displayName.isEmpty().get()) {
@@ -64,6 +79,18 @@ public class VaultSettings {
}
return normalizeDisplayName(name);
}, displayName, path));
migrateLegacySettings(json);
}
@SuppressWarnings("deprecation")
private void migrateLegacySettings(VaultSettingsJson json) {
// implicit migration of 1.6.x legacy setting "customMountPath" / "winDriveLetter":
if (json.useCustomMountPath && !Strings.isNullOrEmpty(json.customMountPath)) {
this.mountPoint.set(Path.of(json.customMountPath));
} else if (!Strings.isNullOrEmpty(json.winDriveLetter)) {
this.mountPoint.set(Path.of(json.winDriveLetter + ":\\"));
}
}
Observable[] observables() {
@@ -71,7 +98,9 @@ public class VaultSettings {
}
public static VaultSettings withRandomId() {
return new VaultSettings(generateId());
var defaults = new VaultSettingsJson();
defaults.id = generateId();
return new VaultSettings(defaults);
}
private static String generateId() {
@@ -80,6 +109,23 @@ public class VaultSettings {
return BaseEncoding.base64Url().encode(randomBytes);
}
public VaultSettingsJson serialized() {
var json = new VaultSettingsJson();
json.id = id;
json.path = path.map(Path::toString).getValue();
json.displayName = displayName.get();
json.unlockAfterStartup = unlockAfterStartup.get();
json.revealAfterMount = revealAfterMount.get();
json.usesReadOnlyMode = usesReadOnlyMode.get();
json.mountFlags = mountFlags.get();
json.maxCleartextFilenameLength = maxCleartextFilenameLength.get();
json.actionAfterUnlock = actionAfterUnlock.get();
json.autoLockWhenIdle = autoLockWhenIdle.get();
json.autoLockIdleSeconds = autoLockIdleSeconds.get();
json.mountPoint = mountPoint.map(Path::toString).getValue();
return json;
}
//visible for testing
static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
@@ -94,6 +140,7 @@ public class VaultSettings {
}
/* Getter/Setter */
// TODO: remove accessors, make fields public
public String getId() {
return id;

View File

@@ -0,0 +1,62 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class VaultSettingsJson {
@JsonProperty(value = "id", required = true)
String id;
@JsonProperty(value = "path")
String path;
@JsonProperty("displayName")
String displayName;
@JsonProperty("unlockAfterStartup")
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
@JsonProperty("revealAfterMount")
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
@JsonProperty("mountPoint")
String mountPoint;
@JsonProperty("usesReadOnlyMode")
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
@JsonProperty("mountFlags")
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
@JsonProperty("maxCleartextFilenameLength")
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
@JsonProperty("actionAfterUnlock")
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
@JsonProperty("autoLockWhenIdle")
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
@JsonProperty("autoLockIdleSeconds")
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "winDriveLetter", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String winDriveLetter;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "useCustomMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
@JsonAlias("usesIndividualMountPath")
boolean useCustomMountPath;
@Deprecated(since = "1.7.0")
@JsonProperty(value = "customMountPath", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
@JsonAlias("individualMountPath")
String customMountPath;
}

View File

@@ -1,142 +0,0 @@
/*******************************************************************************
* 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.common.settings;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
class VaultSettingsJsonAdapter {
private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class);
public void write(JsonWriter out, VaultSettings value) throws IOException {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
out.name("displayName").value(value.displayName().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
var mountPoint = value.mountPoint().get();
out.name("mountPoint").value(mountPoint != null ? mountPoint.toAbsolutePath().toString() : null);
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
out.name("mountFlags").value(value.mountFlags().get());
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
out.name("autoLockWhenIdle").value(value.autoLockWhenIdle().get());
out.name("autoLockIdleSeconds").value(value.autoLockIdleSeconds().get());
out.endObject();
}
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
String displayName = null;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean revealAfterMount = VaultSettings.DEFAULT_REVEAL_AFTER_MOUNT;
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
Path mountPoint = null;
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
//legacy from 1.6.x
boolean useCustomMountPath = false;
String customMountPath = "";
String winDriveLetter = "";
//legacy end
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "id" -> id = in.nextString();
case "path" -> path = in.nextString();
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
case "displayName" -> displayName = in.nextString();
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
case "mountFlags" -> mountFlags = in.nextString();
case "mountPoint" -> {
if (JsonToken.NULL == in.peek()) {
in.nextNull();
} else {
mountPoint = parseMountPoint(in.nextString());
}
}
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
//legacy from 1.6.x
case "winDriveLetter" -> winDriveLetter = in.nextString();
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
//legacy end
default -> {
LOG.warn("Unsupported vault setting found in JSON: {}", name);
in.skipValue();
}
}
}
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
vaultSettings.displayName().set(displayName);
} else {
vaultSettings.displayName().set(mountName);
}
vaultSettings.path().set(Paths.get(path));
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.revealAfterMount().set(revealAfterMount);
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
vaultSettings.mountFlags().set(mountFlags);
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
vaultSettings.mountPoint().set(mountPoint);
//legacy from 1.6.x
if(useCustomMountPath && !customMountPath.isBlank()) {
vaultSettings.mountPoint().set(parseMountPoint(customMountPath));
} else if(!winDriveLetter.isBlank() ) {
vaultSettings.mountPoint().set(parseMountPoint(winDriveLetter+":\\"));
}
//legacy end
return vaultSettings;
}
private Path parseMountPoint(String mountPoint) {
try {
return Path.of(mountPoint);
} catch (InvalidPathException e) {
LOG.warn("Invalid string as mount point. Defaulting to null.");
return null;
}
}
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
try {
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
}
}
}

View File

@@ -1,9 +1,13 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum WhenUnlocked {
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
ASK("vaultOptions.general.actionAfterUnlock.ask");
@JsonEnumDefaultValue ASK("vaultOptions.general.actionAfterUnlock.ask");
private String displayName;

View File

@@ -34,7 +34,7 @@ public class SupportedLanguages {
var collator = Collator.getInstance(preferredLocale);
collator.setStrength(Collator.PRIMARY);
var sorted = new ArrayList<String>();
sorted.add(0, Settings.DEFAULT_LANGUAGE);
sorted.add(0, null);
sorted.add(1, ENGLISH);
LANGUAGE_TAGS.stream() //
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //

View File

@@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicReference;
public class VolumePreferencesController implements FxController {
private static final String DOCS_MOUNTING_URL = "https://docs.cryptomator.org/en/1.7/desktop/volume-type/";
private static final int MIN_PORT = 1024;
private static final int MAX_PORT = 65535;
private final Settings settings;
private final ObservableValue<MountService> selectedMountService;
@@ -85,7 +87,7 @@ public class VolumePreferencesController implements FxController {
try {
int port = Integer.parseInt(loopbackPortField.getText());
return port == 0 // choose port automatically
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
|| port >= MIN_PORT && port <= MAX_PORT; // port within range
} catch (NumberFormatException e) {
return false;
}

View File

@@ -1,61 +0,0 @@
/*******************************************************************************
* 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.common.settings;
import org.cryptomator.common.Environment;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import java.io.IOException;
public class SettingsJsonAdapterTest {
private final Environment env = Mockito.mock(Environment.class);
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(env);
@Test
public void testDeserialize() throws IOException {
String json = """
{
"directories": [
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
],
"autoCloseVaults" : true,
"checkForUpdatesEnabled": true,
"port": 8080,
"language": "de-DE",
"numTrayNotifications": 42
}
""";
Settings settings = adapter.fromJson(json);
Assertions.assertTrue(settings.checkForUpdates().get());
Assertions.assertEquals(2, settings.getDirectories().size());
Assertions.assertEquals(8080, settings.port().get());
Assertions.assertEquals(true, settings.autoCloseVaults().get());
Assertions.assertEquals("de-DE", settings.languageProperty().get());
Assertions.assertEquals(42, settings.numTrayNotifications().get());
}
@SuppressWarnings("SpellCheckingInspection")
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
@ValueSource(strings = { //
"", //
"<html>", //
"{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
Assertions.assertThrows(IOException.class, () -> {
adapter.fromJson(input);
});
}
}

View File

@@ -0,0 +1,79 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.IOException;
import java.util.List;
import static org.hamcrest.CoreMatchers.containsString;
public class SettingsJsonTest {
@Test
public void testDeserialize() throws IOException {
String jsonStr = """
{
"directories": [
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X", "shouldBeIgnored": true},
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y", "mountFlags":"--foo --bar"}
],
"autoCloseVaults" : true,
"checkForUpdatesEnabled": true,
"port": 8080,
"language": "de-DE",
"numTrayNotifications": 42
}
""";
var jsonObj = new ObjectMapper().reader().readValue(jsonStr, SettingsJson.class);
Assertions.assertTrue(jsonObj.checkForUpdatesEnabled);
Assertions.assertEquals(2, jsonObj.directories.size());
Assertions.assertEquals("/vault1", jsonObj.directories.get(0).path);
Assertions.assertEquals("/vault2", jsonObj.directories.get(1).path);
Assertions.assertEquals("--foo --bar", jsonObj.directories.get(1).mountFlags);
Assertions.assertEquals(8080, jsonObj.port);
Assertions.assertTrue(jsonObj.autoCloseVaults);
Assertions.assertEquals("de-DE", jsonObj.language);
Assertions.assertEquals(42, jsonObj.numTrayNotifications);
}
@SuppressWarnings("SpellCheckingInspection")
@ParameterizedTest(name = "throw JacksonException for input: {0}")
@ValueSource(strings = { //
"", //
"<html>", //
"{invalidjson}" //
})
public void testDeserializeMalformed(String input) {
var objectMapper = new ObjectMapper().reader();
Assertions.assertThrows(JacksonException.class, () -> {
objectMapper.readValue(input, SettingsJson.class);
});
}
@Test
public void testSerialize() throws JsonProcessingException {
var jsonObj = new SettingsJson();
jsonObj.directories = List.of(new VaultSettingsJson(), new VaultSettingsJson());
jsonObj.directories.get(0).id = "test";
jsonObj.theme = UiTheme.DARK;
jsonObj.showTrayIcon = false;
var jsonStr = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
MatcherAssert.assertThat(jsonStr, containsString("\"theme\" : \"DARK\""));
MatcherAssert.assertThat(jsonStr, containsString("\"showTrayIcon\" : false"));
MatcherAssert.assertThat(jsonStr, containsString("\"useKeychain\" : true"));
MatcherAssert.assertThat(jsonStr, containsString("\"actionAfterUnlock\" : \"ASK\""));
}
}

View File

@@ -18,7 +18,7 @@ public class SettingsTest {
Environment env = Mockito.mock(Environment.class);
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
Settings settings = new Settings(env);
Settings settings = Settings.create(env);
settings.setSaveCmd(changeListener);
VaultSettings vaultSettings = VaultSettings.withRandomId();
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);

View File

@@ -1,67 +0,0 @@
/*******************************************************************************
* 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.common.settings;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Paths;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VaultSettingsJsonAdapterTest {
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
@Test
public void testDeserialize() throws IOException {
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
VaultSettings vaultSettings = adapter.read(jsonReader);
assertAll(
() -> assertEquals("foo", vaultSettings.getId()),
() -> assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get()),
() -> assertEquals("test", vaultSettings.displayName().get()),
() -> assertEquals("--foo --bar", vaultSettings.mountFlags().get())
);
}
@SuppressWarnings("SpellCheckingInspection")
@Test
public void testSerialize() throws IOException {
VaultSettings vaultSettings = new VaultSettings("test");
vaultSettings.path().set(Paths.get("/foo/bar"));
vaultSettings.displayName().set("mountyMcMountFace");
vaultSettings.mountFlags().set("--foo --bar");
StringWriter buf = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(buf);
adapter.write(jsonWriter, vaultSettings);
String result = buf.toString();
assertAll(
() -> assertThat(result, containsString("\"id\":\"test\"")),
() -> {
if (System.getProperty("os.name").contains("Windows")) {
assertThat(result, containsString("\"path\":\"\\\\foo\\\\bar\""));
} else {
assertThat(result, containsString("\"path\":\"/foo/bar\""));
}
},
() -> assertThat(result, containsString("\"displayName\":\"mountyMcMountFace\"")),
() -> assertThat(result, containsString("\"mountFlags\":\"--foo --bar\""))
);
}
}