diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 62b9eed9..5671ec8d 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -205,24 +205,19 @@ public class AppPrefs { private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class); - // External startup behaviour - // ========================== - private final ObjectProperty internalStorageDirectory = + // Storage + // ======= + private final ObjectProperty storageDirectory = typed(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), Path.class); - private final ObjectProperty effectiveStorageDirectory = STORAGE_DIR_FIXED - ? new SimpleObjectProperty<>(AppProperties.get().getDataDir().resolve("storage")) - : internalStorageDirectory; - private final StringField storageDirectoryControl = PrefFields.ofPath(effectiveStorageDirectory) - .editable(!STORAGE_DIR_FIXED) + private final StringField storageDirectoryControl = PrefFields.ofPath(storageDirectory) .validate( CustomValidators.absolutePath(), - CustomValidators.directory(), - CustomValidators.emptyStorageDirectory()); - private final ObjectProperty internalLogLevel = - typed(new SimpleObjectProperty<>(DEFAULT_LOG_LEVEL), String.class); + CustomValidators.directory()); // Log level // ========= + private final ObjectProperty internalLogLevel = + typed(new SimpleObjectProperty<>(DEFAULT_LOG_LEVEL), String.class); private final ObjectProperty effectiveLogLevel = LOG_LEVEL_FIXED ? new SimpleObjectProperty<>(System.getProperty(LOG_LEVEL_PROP).toLowerCase()) : internalLogLevel; @@ -230,6 +225,7 @@ public class AppPrefs { logLevelList, effectiveLogLevel) .editable(!LOG_LEVEL_FIXED) .render(() -> new SimpleComboBoxControl<>()); + // Developer mode // ============== private final BooleanProperty internalDeveloperMode = typed(new SimpleBooleanProperty(false), Boolean.class); @@ -335,7 +331,7 @@ public class AppPrefs { } public ObservableValue storageDirectory() { - return effectiveStorageDirectory; + return storageDirectory; } public ReadOnlyProperty logLevel() { @@ -535,9 +531,9 @@ public class AppPrefs { automaticallyCheckForUpdatesField, automaticallyCheckForUpdates), Setting.of("updateToPrereleases", checkForPrereleasesField, checkForPrereleases)), - Group.of( + group( "advanced", - Setting.of("storageDirectory", storageDirectoryControl, internalStorageDirectory), + STORAGE_DIR_FIXED ? null : Setting.of("storageDirectory", storageDirectoryControl, storageDirectory), Setting.of("logLevel", logLevelField, internalLogLevel), Setting.of("developerMode", developerModeField, internalDeveloperMode))), Category.of( @@ -602,6 +598,10 @@ public class AppPrefs { return AppPreferencesFx.of(cats); } + private Group group(String name, Setting... settings) { + return Group.of(name, Arrays.stream(settings).filter(setting -> setting != null).toArray(Setting[]::new)); + } + private class PrefsHandlerImpl implements PrefsHandler { private final List categories; diff --git a/app/src/main/java/io/xpipe/app/prefs/CustomValidators.java b/app/src/main/java/io/xpipe/app/prefs/CustomValidators.java index e60eaad8..7399574a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/CustomValidators.java +++ b/app/src/main/java/io/xpipe/app/prefs/CustomValidators.java @@ -3,7 +3,6 @@ package io.xpipe.app.prefs; import com.dlsc.formsfx.model.validators.CustomValidator; import com.dlsc.formsfx.model.validators.Validator; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -30,25 +29,4 @@ public class CustomValidators { }, "notADirectory"); } - - public static Validator emptyStorageDirectory() { - return CustomValidator.forPredicate( - (String s) -> { - var p = Path.of(s); - if (AppPrefs.get() == null) { - return true; - } - - if (p.equals(AppPrefs.get().storageDirectory().getValue())) { - return true; - } - - try { - return Files.list(p).findAny().isEmpty(); - } catch (IOException ignored) { - return false; - } - }, - "notAnEmptyDirectory"); - } } diff --git a/app/src/main/java/io/xpipe/app/prefs/PrefFields.java b/app/src/main/java/io/xpipe/app/prefs/PrefFields.java index e82464bb..7c70e6e7 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PrefFields.java +++ b/app/src/main/java/io/xpipe/app/prefs/PrefFields.java @@ -3,34 +3,39 @@ package io.xpipe.app.prefs; import com.dlsc.formsfx.model.structure.StringField; import com.dlsc.preferencesfx.formsfx.view.controls.SimpleChooserControl; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.fxcomps.util.BindingsHelper; +import io.xpipe.app.fxcomps.util.PlatformThread; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.util.StringConverter; import java.nio.file.Path; -import java.util.Objects; public class PrefFields { public static StringField ofPath(ObjectProperty fileProperty) { - StringProperty stringProperty = new SimpleStringProperty(); - stringProperty.bindBidirectional(fileProperty, new StringConverter<>() { - @Override - public String toString(Path file) { - if (Objects.isNull(file)) { - return ""; - } - return file.toString(); - } + StringProperty stringProperty = new SimpleStringProperty(fileProperty.getValue().toString()); - @Override - public Path fromString(String value) { - return Path.of(value); - } + // Prevent garbage collection of this due to how preferencesfx handles properties via bindings + BindingsHelper.linkPersistently(fileProperty, stringProperty); + + stringProperty.addListener((observable, oldValue, newValue) -> { + fileProperty.setValue(newValue != null ? Path.of(newValue) : null); }); + + fileProperty.addListener((observable, oldValue, newValue) -> { + PlatformThread.runLaterIfNeeded(() -> { + stringProperty.setValue(newValue != null ? newValue.toString() : ""); + }); + }); + return StringField.ofStringType(stringProperty) - .render(() -> new SimpleChooserControl( - AppI18n.get("browse"), fileProperty.getValue().toFile(), true)); + .render(() -> { + var c = new SimpleChooserControl( + AppI18n.get("browse"), fileProperty.getValue().toFile(), true); + c.setMinWidth(600); + c.setPrefWidth(600); + return c; + }); } } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index d100220f..4b082f36 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -145,10 +145,6 @@ public abstract class DataStorage { } } - protected Path getSourcesDir() { - return dir.resolve("sources"); - } - protected Path getStoresDir() { return dir.resolve("stores"); } diff --git a/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java b/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java index 3ba0498f..ca588236 100644 --- a/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java @@ -17,14 +17,10 @@ public class ImpersistentStorage extends DataStorage { @Override public void save() { - var sourcesDir = getSourcesDir(); var storesDir = getStoresDir(); TrackEvent.info("Storage persistence is disabled. Deleting storage contents ..."); try { - if (Files.exists(sourcesDir)) { - FileUtils.cleanDirectory(sourcesDir.toFile()); - } if (Files.exists(storesDir)) { FileUtils.cleanDirectory(storesDir.toFile()); } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index 5603400b..5e851c14 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -24,8 +24,6 @@ public class StandardStorage extends DataStorage { } private void deleteLeftovers() { - var entriesDir = getSourcesDir().resolve("entries"); - var collectionsDir = getSourcesDir().resolve("collections"); var storesDir = getStoresDir(); // Delete leftover directories in entries dir @@ -63,14 +61,10 @@ public class StandardStorage extends DataStorage { public synchronized void load() { var newSession = isNewSession(); - var entriesDir = getSourcesDir().resolve("entries"); - var collectionsDir = getSourcesDir().resolve("collections"); var storesDir = getStoresDir(); var streamsDir = getStreamsDir(); try { - FileUtils.forceMkdir(entriesDir.toFile()); - FileUtils.forceMkdir(collectionsDir.toFile()); FileUtils.forceMkdir(storesDir.toFile()); FileUtils.forceMkdir(streamsDir.toFile()); } catch (Exception e) { @@ -120,14 +114,10 @@ public class StandardStorage extends DataStorage { } public synchronized void save() { - var entriesDir = getSourcesDir().resolve("entries"); - var collectionsDir = getSourcesDir().resolve("collections"); - try { - FileUtils.forceMkdir(entriesDir.toFile()); - FileUtils.forceMkdir(collectionsDir.toFile()); + FileUtils.forceMkdir(getStoresDir().toFile()); } catch (Exception e) { - ErrorEvent.fromThrowable(e).terminal(true).build().handle(); + ErrorEvent.fromThrowable(e).description("Unable to create storage directory " + getStoresDir()).terminal(true).build().handle(); } // Save stores diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties index fc4f7337..c3ac1fda 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/preferences_en.properties @@ -56,7 +56,7 @@ automaticallyUpdateDescription=When enabled, new release information is automati sendAnonymousErrorReports=Send anonymous error reports sendUsageStatistics=Send anonymous usage statistics storageDirectory=Storage directory -storageDirectoryDescription=The location where XPipe should store all connection and data source information. +storageDirectoryDescription=The location where XPipe should store all connection information. This setting will only be applied at the next restart. When changing this, the data in the old directory is not copied to the new one. logLevel=Log level appBehaviour=Application behaviour logLevelDescription=The log level that should be used when writing log files.