From c80a31bffe4e4920a010d1aea55343e1d96a6353 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 16 Oct 2023 03:37:22 +0000 Subject: [PATCH] Performance improvements --- app/build.gradle | 56 +-- .../io/xpipe/app/browser/BrowserComp.java | 4 +- .../java/io/xpipe/app/comp/AppLayoutComp.java | 33 +- .../xpipe/app/comp/base/MultiContentComp.java | 8 +- .../storage/store/StoreEntryListComp.java | 25 +- .../comp/storage/store/StoreEntryWrapper.java | 14 +- .../app/comp/storage/store/StoreSection.java | 53 ++- .../comp/storage/store/StoreViewState.java | 4 +- .../io/xpipe/app/core/mode/PlatformMode.java | 1 + .../exchange/cli/RemoveStoreExchangeImpl.java | 3 +- .../exchange/cli/RenameStoreExchangeImpl.java | 7 +- .../io/xpipe/app/ext/DataStoreProvider.java | 4 + .../app/fxcomps/util/BindingsHelper.java | 20 +- .../app/storage/DataStateProviderImpl.java | 9 +- .../io/xpipe/app/storage/DataStorage.java | 356 ++++++++---------- .../io/xpipe/app/storage/DataStoreEntry.java | 21 +- .../app/storage/ImpersistentStorage.java | 7 - .../io/xpipe/app/storage/StandardStorage.java | 13 +- .../style/application-choice-comp.css | 15 - .../app/resources/style/char-choice-comp.css | 18 - .../resources/style/data-source-config.css | 4 - .../resources/style/data-source-dialog.css | 43 --- .../resources/style/data-source-preview.css | 23 -- .../resources/style/data-source-retrieve.css | 0 .../style/data-source-type-choice-comp.css | 3 - .../app/resources/style/table-mapping.css | 22 -- .../io/xpipe/beacon/BeaconConnection.java | 22 -- .../xpipe/core/store/InternalStreamStore.java | 41 -- .../io/xpipe/core/util/DataStateProvider.java | 4 - .../java/io/xpipe/core/util/UuidHelper.java | 5 +- dist/jpackage.gradle | 2 +- .../ext/base/InternalStreamProvider.java | 25 -- .../action/DeleteStoreChildrenAction.java | 4 +- .../ext/base/action/RefreshStoreAction.java | 2 +- .../ext/base/script/ScriptGroupStore.java | 2 +- ext/base/src/main/java/module-info.java | 2 - gradle/gradle_scripts/javafx.gradle | 14 +- 37 files changed, 319 insertions(+), 570 deletions(-) delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/application-choice-comp.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/char-choice-comp.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/data-source-config.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/data-source-dialog.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/data-source-preview.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/data-source-retrieve.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/data-source-type-choice-comp.css delete mode 100644 app/src/main/resources/io/xpipe/app/resources/style/table-mapping.css delete mode 100644 core/src/main/java/io/xpipe/core/store/InternalStreamStore.java delete mode 100644 ext/base/src/main/java/io/xpipe/ext/base/InternalStreamProvider.java diff --git a/app/build.gradle b/app/build.gradle index a979e3337..089ca0b2e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,33 +89,33 @@ project.allExtensions.forEach((Project p) -> { } }) - -List jvmRunArgs = [ - "--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix", - "--add-exports", "javafx.graphics/com.sun.javafx.stage=com.jfoenix", - "--add-exports", "javafx.base/com.sun.javafx.binding=com.jfoenix", - "--add-exports", "javafx.base/com.sun.javafx.event=com.jfoenix", - "--add-exports", "javafx.controls/com.sun.javafx.scene.control=com.jfoenix", - "--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix", - "--add-exports", "javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", - "--add-exports", "javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls", - "--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.app", - "--add-opens", "java.base/java.lang=io.xpipe.app", - "--add-opens", "java.base/java.lang.reflect=com.jfoenix", - "--add-opens", "java.base/java.lang.reflect=com.jfoenix", - "--add-opens", "java.base/java.lang=io.xpipe.core", - "--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app", - "--add-opens", "java.desktop/java.awt=io.xpipe.app", - "--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app", - "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app', - "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app', - "-Xmx8g", - "-Dio.xpipe.app.arch=$rootProject.arch", - // "-XX:+ExitOnOutOfMemoryError", - "-Dfile.encoding=UTF-8", - '-XX:+UseZGC', - "-Dvisualvm.display.name=XPipe" -] +project.ext { + jvmRunArgs = [ + "--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix", + "--add-exports", "javafx.graphics/com.sun.javafx.stage=com.jfoenix", + "--add-exports", "javafx.base/com.sun.javafx.binding=com.jfoenix", + "--add-exports", "javafx.base/com.sun.javafx.event=com.jfoenix", + "--add-exports", "javafx.controls/com.sun.javafx.scene.control=com.jfoenix", + "--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix", + "--add-exports", "javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", + "--add-exports", "javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls", + "--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.app", + "--add-opens", "java.base/java.lang=io.xpipe.app", + "--add-opens", "java.base/java.lang.reflect=com.jfoenix", + "--add-opens", "java.base/java.lang.reflect=com.jfoenix", + "--add-opens", "java.base/java.lang=io.xpipe.core", + "--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app", + "--add-opens", "java.desktop/java.awt=io.xpipe.app", + "--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app", + "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app', + "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app', + "-Xmx8g", + "-Dio.xpipe.app.arch=$rootProject.arch", + "-Dfile.encoding=UTF-8", + '-XX:+UseZGC', + "-Dvisualvm.display.name=XPipe" + ] +} import org.gradle.internal.os.OperatingSystem @@ -133,6 +133,7 @@ application { mainModule = 'io.xpipe.app' mainClass = 'io.xpipe.app.Main' applicationDefaultJvmArgs = jvmRunArgs + applicationDefaultJvmArgs.add('-XX:+EnableDynamicAgentLoading') } run { @@ -166,6 +167,7 @@ task runAttachedDebugger(type: JavaExec) { "-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.1.jar=port:7857,host:localhost", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0" ) + jvmArgs += '-XX:+EnableDynamicAgentLoading' systemProperties run.systemProperties } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java index 35be1d8d0..6feeba6ec 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java @@ -21,7 +21,7 @@ import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ObservableBooleanValue; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.geometry.Insets; import javafx.geometry.Orientation; @@ -136,7 +136,7 @@ public class BrowserComp extends SimpleComp { } private Node createTabs() { - var multi = new MultiContentComp(Map., ObservableBooleanValue>of( + var multi = new MultiContentComp(Map., ObservableValue>of( Comp.of(() -> createTabPane()), BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())), new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)), diff --git a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java index 82086248e..4f3007899 100644 --- a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java @@ -1,5 +1,6 @@ package io.xpipe.app.comp; +import io.xpipe.app.comp.base.MultiContentComp; import io.xpipe.app.comp.base.SideMenuBarComp; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppLayoutModel; @@ -8,12 +9,11 @@ import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.prefs.AppPrefs; +import javafx.beans.binding.Bindings; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; -import java.util.HashMap; -import java.util.Map; +import java.util.stream.Collectors; public class AppLayoutComp extends Comp> { @@ -21,35 +21,26 @@ public class AppLayoutComp extends Comp> { @Override public CompStructure createBase() { - var map = new HashMap(); - getRegion(model.getEntries().get(0), map); - getRegion(model.getEntries().get(1), map); + var multi = new MultiContentComp(model.getEntries().stream() + .collect(Collectors.toMap( + entry -> entry.comp(), + entry -> PlatformThread.sync(Bindings.createBooleanBinding( + () -> { + return model.getSelected().getValue().equals(entry); + }, + model.getSelected()))))); var pane = new BorderPane(); var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries()); - pane.setCenter(getRegion(model.getSelected().getValue(), map)); + pane.setCenter(multi.createRegion()); pane.setRight(sidebar.createRegion()); pane.getStyleClass().add("background"); model.getSelected().addListener((c, o, n) -> { if (o != null && o.equals(model.getEntries().get(2))) { AppPrefs.get().save(); } - - PlatformThread.runLaterIfNeeded(() -> { - pane.setCenter(getRegion(n, map)); - }); }); AppFont.normal(pane); return new SimpleCompStructure<>(pane); } - - private Region getRegion(AppLayoutModel.Entry entry, Map map) { - if (map.containsKey(entry)) { - return map.get(entry); - } - - Region r = entry.comp().createRegion(); - map.put(entry, r); - return r; - } } diff --git a/app/src/main/java/io/xpipe/app/comp/base/MultiContentComp.java b/app/src/main/java/io/xpipe/app/comp/base/MultiContentComp.java index f3e7b0481..852b08438 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/MultiContentComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/MultiContentComp.java @@ -4,7 +4,7 @@ import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.SimpleChangeListener; -import javafx.beans.value.ObservableBooleanValue; +import javafx.beans.value.ObservableValue; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; @@ -12,9 +12,9 @@ import java.util.Map; public class MultiContentComp extends SimpleComp { - private final Map, ObservableBooleanValue> content; + private final Map, ObservableValue> content; - public MultiContentComp(Map, ObservableBooleanValue> content) { + public MultiContentComp(Map, ObservableValue> content) { this.content = content; } @@ -22,7 +22,7 @@ public class MultiContentComp extends SimpleComp { protected Region createSimple() { var stack = new StackPane(); stack.setPickOnBounds(false); - for (Map.Entry, ObservableBooleanValue> entry : content.entrySet()) { + for (Map.Entry, ObservableValue> entry : content.entrySet()) { var region = entry.getKey().createRegion(); stack.getChildren().add(region); SimpleChangeListener.apply(PlatformThread.sync(entry.getValue()), val -> { diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java index 9a351a745..45656312e 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java @@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.app.fxcomps.util.BindingsHelper; import javafx.beans.binding.Bindings; -import javafx.beans.value.ObservableBooleanValue; +import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; import javafx.scene.layout.Region; @@ -17,11 +17,15 @@ import java.util.List; public class StoreEntryListComp extends SimpleComp { private Comp createList() { - var topLevel = StoreViewState.get().getTopLevelSection(); - var content = new ListBoxViewComp<>(topLevel.getShownChildren(), topLevel.getAllChildren(), (StoreSection e) -> { - var custom = StoreSection.customSection(e, true).hgrow(); - return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10))).styleClass("top"); - }).apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0))); + var content = new ListBoxViewComp<>( + StoreViewState.get().getCurrentTopLevelSection().getShownChildren(), + StoreViewState.get().getCurrentTopLevelSection().getAllChildren(), + (StoreSection e) -> { + var custom = StoreSection.customSection(e, true).hgrow(); + return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10))) + .styleClass("top"); + }) + .apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0))); return content.styleClass("store-list-comp"); } @@ -33,18 +37,19 @@ public class StoreEntryListComp extends SimpleComp { return initialCount == StoreViewState.get().getAllEntries().size(); }, StoreViewState.get().getAllEntries()); - var map = new LinkedHashMap, ObservableBooleanValue>(); + var map = new LinkedHashMap, ObservableValue>(); map.put( createList(), - BindingsHelper.persist( - Bindings.not(Bindings.isEmpty(StoreViewState.get().getTopLevelSection().getShownChildren())))); + BindingsHelper.persist(Bindings.not(Bindings.isEmpty( + StoreViewState.get().getCurrentTopLevelSection().getShownChildren())))); map.put(new StoreIntroComp(), showIntro); map.put( new StoreNotFoundComp(), BindingsHelper.persist(Bindings.and( Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())), - Bindings.isEmpty(StoreViewState.get().getTopLevelSection().getShownChildren())))); + Bindings.isEmpty( + StoreViewState.get().getCurrentTopLevelSection().getShownChildren())))); return new MultiContentComp(map).createRegion(); } } diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java index 82bc242d2..2e4b129db 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java @@ -70,18 +70,6 @@ public class StoreEntryWrapper { }); } - private StoreEntryWrapper computeDisplayParent() { - if (StoreViewState.get() == null) { - return null; - } - - var p = DataStorage.get().getDisplayParent(entry).orElse(null); - return StoreViewState.get().getAllEntries().stream() - .filter(storeEntryWrapper -> storeEntryWrapper.getEntry().equals(p)) - .findFirst() - .orElse(null); - } - public boolean isInStorage() { return DataStorage.get().getStoreEntries().contains(entry); } @@ -92,7 +80,7 @@ public class StoreEntryWrapper { public void delete() { ThreadHelper.runAsync(() -> { - DataStorage.get().deleteChildren(this.entry, true); + DataStorage.get().deleteChildren(this.entry); DataStorage.get().deleteStoreEntry(this.entry); }); } diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java index f16a7ac78..06b7557db 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java @@ -10,12 +10,10 @@ import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import lombok.Value; import java.util.Comparator; -import java.util.List; import java.util.function.Predicate; @Value @@ -33,7 +31,6 @@ public class StoreSection { StoreEntryWrapper wrapper; ObservableList allChildren; ObservableList shownChildren; - ObservableList shownEntries; int depth; ObservableBooleanValue showDetails; @@ -56,23 +53,14 @@ public class StoreSection { } else { this.showDetails = new SimpleBooleanProperty(true); } - - this.shownEntries = FXCollections.observableArrayList(); - this.shownChildren.addListener((ListChangeListener) c -> { - shownEntries.clear(); - addShown(shownEntries); - }); - } - - private void addShown(List list) { - getShownChildren().forEach(shown -> { - list.add(shown.getWrapper()); - shown.addShown(list); - }); } private static ObservableList sorted( ObservableList list, ObservableValue category) { + if (category == null) { + return list; + } + var c = Comparator.comparingInt( value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1); var mapped = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode()); @@ -96,21 +84,26 @@ public class StoreSection { Predicate entryFilter, ObservableStringValue filterString, ObservableValue category) { - var cached = BindingsHelper.cachedMappedContentBinding( - all, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category)); - var ordered = sorted(cached, category); var topLevel = BindingsHelper.filteredContentBinding( + all, + section -> { + return DataStorage.get().isRootEntry(section.getEntry()); + }, + category); + var cached = BindingsHelper.cachedMappedContentBinding( + topLevel, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category)); + var ordered = sorted(cached, category); + var shown = BindingsHelper.filteredContentBinding( ordered, section -> { - var sameCategory = - category.getValue().contains(section.getWrapper().getEntry()); - var showFilter = section.shouldShow(filterString.get()); + var showFilter = filterString == null || section.shouldShow(filterString.get()); var matchesSelector = section.anyMatches(entryFilter); - return DataStorage.get().isRootEntry(section.getWrapper().getEntry()) && showFilter && sameCategory && matchesSelector; + var sameCategory = category == null || category.getValue().contains(section.getWrapper().getEntry()); + return showFilter && matchesSelector && sameCategory; }, category, filterString); - return new StoreSection(null, cached, topLevel, 0); + return new StoreSection(null, ordered, shown, 0); } private static StoreSection create( @@ -125,10 +118,12 @@ public class StoreSection { } var allChildren = BindingsHelper.filteredContentBinding(all, other -> { - return DataStorage.get() - .getDisplayParent(other.getEntry()) - .map(found -> found.equals(e.getEntry())) - .orElse(false); +// if (true) return DataStorage.get() +// .getDisplayParent(other.getEntry()) +// .map(found -> found.equals(e.getEntry())) +// .orElse(false); + // This check is fast as the children are cached in the storage + return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry()); }); var cached = BindingsHelper.cachedMappedContentBinding( allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category)); @@ -136,7 +131,7 @@ public class StoreSection { var filtered = BindingsHelper.filteredContentBinding( ordered, section -> { - return section.shouldShow(filterString.get()) + return (filterString == null || section.shouldShow(filterString.get())) && section.anyMatches(entryFilter); }, category, diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java index 32691e6d6..10e749ab3 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java @@ -34,7 +34,7 @@ public class StoreViewState { FXCollections.observableList(new CopyOnWriteArrayList<>()); @Getter - private final StoreSection topLevelSection; + private final StoreSection currentTopLevelSection; @Getter private final Property activeCategory = new SimpleObjectProperty<>(); @@ -51,7 +51,7 @@ public class StoreViewState { activeCategory.setValue(getAllConnectionsCategory()); ErrorEvent.fromThrowable(exception).handle(); } - topLevelSection = tl; + currentTopLevelSection = tl; } public ObservableList getSortedCategories() { diff --git a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java index e1fe2a0aa..bad75aa50 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java @@ -20,6 +20,7 @@ public abstract class PlatformMode extends OperationMode { @Override public void onSwitchTo() throws Throwable { if (App.getApp() != null) { + StoreViewState.init(); return; } diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java index 40997a352..25ee3208c 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java @@ -5,13 +5,14 @@ import io.xpipe.app.storage.DataStorage; import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.ClientException; import io.xpipe.beacon.exchange.cli.RemoveStoreExchange; +import io.xpipe.core.store.DataStoreId; public class RemoveStoreExchangeImpl extends RemoveStoreExchange implements MessageExchangeImpl { @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { - var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true); + var s = getStoreEntryById(DataStoreId.fromString(msg.getStoreName()), true); if (!s.getConfiguration().isDeletable()) { throw new ClientException("Store is not deletable"); } diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java index e3d7f8110..eccd5546e 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java @@ -1,16 +1,17 @@ package io.xpipe.app.exchange.cli; import io.xpipe.app.exchange.MessageExchangeImpl; -import io.xpipe.app.storage.DataStorage; import io.xpipe.beacon.BeaconHandler; +import io.xpipe.beacon.ClientException; import io.xpipe.beacon.exchange.cli.RenameStoreExchange; +import io.xpipe.core.store.DataStoreId; public class RenameStoreExchangeImpl extends RenameStoreExchange implements MessageExchangeImpl { @Override - public Response handleRequest(BeaconHandler handler, Request msg) { - var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true); + public Response handleRequest(BeaconHandler handler, Request msg) throws ClientException { + var s = getStoreEntryById(DataStoreId.fromString(msg.getStoreName()), true); s.setName(msg.getNewName()); return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java index 0af4b7e27..5afc352c7 100644 --- a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java @@ -98,6 +98,10 @@ public interface DataStoreProvider { } default DataStoreEntry getDisplayParent(DataStoreEntry store) { + return getSyntheticParent(store); + } + + default DataStoreEntry getSyntheticParent(DataStoreEntry store) { return null; } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java index f14f46809..d34f5e725 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java @@ -239,14 +239,18 @@ public class BindingsHelper { } public static ObservableList filteredContentBinding(ObservableList l2, Predicate predicate, Observable... observables) { - return filteredContentBinding(l2, Bindings.createObjectBinding(() -> { - return new Predicate<>() { - @Override - public boolean test(V v) { - return predicate.test(v); - } - }; - }, observables)); + return filteredContentBinding( + l2, + Bindings.createObjectBinding( + () -> { + return new Predicate<>() { + @Override + public boolean test(V v) { + return predicate.test(v); + } + }; + }, + Arrays.stream(observables).filter( Objects::nonNull).toArray(Observable[]::new))); } public static ObservableList filteredContentBinding( diff --git a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java index 52a67424b..8f2a154d2 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java @@ -1,12 +1,10 @@ package io.xpipe.app.storage; -import io.xpipe.core.store.StatefulDataStore; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStoreState; +import io.xpipe.core.store.StatefulDataStore; import io.xpipe.core.util.DataStateProvider; -import java.nio.file.Path; -import java.util.UUID; import java.util.function.Supplier; public class DataStateProviderImpl extends DataStateProvider { @@ -85,9 +83,4 @@ public class DataStateProviderImpl extends DataStateProvider { var entry = DataStorage.get().getStoreEntryIfPresent(store); return entry.isPresent(); } - - @Override - public Path getInternalStreamStore(UUID id) { - return DataStorage.get().getInternalStreamPath(id); - } } 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 7dfc5cae1..ae0a9b50f 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -9,7 +9,7 @@ import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStoreId; import io.xpipe.core.store.FixedChildStore; import io.xpipe.core.store.LocalStore; -import io.xpipe.core.util.FailableRunnable; +import io.xpipe.core.util.UuidHelper; import javafx.util.Pair; import lombok.Getter; import lombok.NonNull; @@ -17,8 +17,10 @@ import lombok.Setter; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class DataStorage { @@ -36,7 +38,7 @@ public abstract class DataStorage { private static DataStorage INSTANCE; protected final Path dir; protected final List storeCategories; - protected final List storeEntries; + protected final Set storeEntries; @Getter @Setter @@ -47,22 +49,10 @@ public abstract class DataStorage { public DataStorage() { this.dir = AppPrefs.get().storageDirectory().getValue(); - this.storeEntries = new CopyOnWriteArrayList<>(); + this.storeEntries = Collections.newSetFromMap(new ConcurrentHashMap<>()); this.storeCategories = new CopyOnWriteArrayList<>(); } - protected void refreshValidities(boolean makeValid) { - var changed = new AtomicBoolean(false); - do { - changed.set(false); - storeEntries.forEach(dataStoreEntry -> { - if (makeValid ? dataStoreEntry.tryMakeValid() : dataStoreEntry.tryMakeInvalid()) { - changed.set(true); - } - }); - } while (changed.get()); - } - public DataStoreCategory getDefaultCategory() { return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow(); } @@ -109,6 +99,80 @@ public abstract class DataStorage { return INSTANCE; } + protected Path getStoresDir() { + return dir.resolve("stores"); + } + + protected Path getStreamsDir() { + return dir.resolve("streams"); + } + + protected Path getCategoriesDir() { + return dir.resolve("categories"); + } + + public void addListener(StorageListener l) { + this.listeners.add(l); + } + + public abstract void load(); + + public void saveAsync() { + // TODO: Don't make this a daemon thread to guarantee proper saving + ThreadHelper.unstarted(this::save).start(); + } + + public abstract void save(); + + public abstract boolean supportsSharing(); + + protected void refreshValidities(boolean makeValid) { + var changed = new AtomicBoolean(false); + do { + changed.set(false); + storeEntries.forEach(dataStoreEntry -> { + if (makeValid ? dataStoreEntry.tryMakeValid() : dataStoreEntry.tryMakeInvalid()) { + changed.set(true); + } + }); + } while (changed.get()); + } + + public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) { + var oldParent = DataStorage.get().getDisplayParent(entry); + var newParent = DataStorage.get().getDisplayParent(newEntry); + var diffParent = Objects.equals(oldParent, newParent); + + newEntry.finalizeEntry(); + + var children = getDeepStoreChildren(entry); + if (!diffParent) { + var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); + listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove)); + } + + entry.applyChanges(newEntry); + entry.initializeEntry(); + + if (!diffParent) { + var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); + listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); + refreshValidities(true); + } + } + + public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) { + var children = getDeepStoreChildren(entry); + var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); + listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove)); + + entry.setCategoryUuid(newCategory.getUuid()); + children.forEach(child -> child.setCategoryUuid(newCategory.getUuid())); + + var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); + listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); + } + public boolean refreshChildren(DataStoreEntry e) { if (!(e.getStore() instanceof FixedHierarchyStore)) { return false; @@ -125,7 +189,9 @@ public abstract class DataStorage { return false; } - var oldChildren = getStoreEntries().stream().filter(other -> e.equals(other.getProvider().getLogicalParent(other))).toList(); + var oldChildren = getStoreEntries().stream() + .filter(other -> e.equals(other.getProvider().getLogicalParent(other))) + .toList(); var toRemove = oldChildren.stream() .filter(entry -> newChildren.stream() .noneMatch( @@ -139,7 +205,8 @@ public abstract class DataStorage { var toUpdate = oldChildren.stream() .map(entry -> { var found = newChildren.stream() - .filter(nc -> nc.getStore().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()) + .filter(nc -> + nc.getStore().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()) .findFirst() .orElse(null); return new Pair<>(entry, found); @@ -152,29 +219,21 @@ public abstract class DataStorage { } deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new)); - addStoreEntriesIfNotPresent(toAdd.stream() - .map(DataStoreEntryRef::get) - .toArray(DataStoreEntry[]::new)); + addStoreEntriesIfNotPresent(toAdd.stream().map(DataStoreEntryRef::get).toArray(DataStoreEntry[]::new)); toUpdate.forEach(pair -> { - propagateUpdate( - () -> { - pair.getKey().setStoreInternal(pair.getValue().getStore(), false); - }, - pair.getKey()); + pair.getKey().setStoreInternal(pair.getValue().getStore(), false); }); - e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).toList()); - saveAsync(); + e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).collect(Collectors.toSet())); + saveAsync(); return !newChildren.isEmpty(); } public void deleteWithChildren(DataStoreEntry... entries) { var toDelete = Arrays.stream(entries) .flatMap(entry -> { - // Reverse to delete deepest children first - var ordered = getStoreChildren(entry, true); - Collections.reverse(ordered); - ordered.add(entry); - return ordered.stream(); + var c = getDeepStoreChildren(entry); + c.add(entry); + return c.stream(); }) .toList(); @@ -185,22 +244,17 @@ public abstract class DataStorage { saveAsync(); } - public void deleteChildren(DataStoreEntry e, boolean deep) { - // Reverse to delete deepest children first - var ordered = getStoreChildren(e, deep); - Collections.reverse(ordered); - - ordered.forEach(entry -> entry.finalizeEntry()); - this.storeEntries.removeAll(ordered); - this.listeners.forEach(l -> l.onStoreRemove(ordered.toArray(DataStoreEntry[]::new))); + public void deleteChildren(DataStoreEntry e) { + var c = getDeepStoreChildren(e); + c.forEach(entry -> entry.finalizeEntry()); + this.storeEntries.removeAll(c); + this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new))); refreshValidities(false); saveAsync(); } public boolean isRootEntry(DataStoreEntry entry) { - var noParent = DataStorage.get() - .getDisplayParent(entry) - .isEmpty(); + var noParent = DataStorage.get().getDisplayParent(entry).isEmpty(); var diffParentCategory = DataStorage.get() .getDisplayParent(entry) .map(p -> !p.getCategoryUuid().equals(entry.getCategoryUuid())) @@ -229,6 +283,19 @@ public abstract class DataStorage { return current; } + public Optional getSyntheticParent(DataStoreEntry entry) { + if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { + return Optional.empty(); + } + + try { + var provider = entry.getProvider(); + return Optional.ofNullable(provider.getSyntheticParent(entry)); + } catch (Exception ex) { + return Optional.empty(); + } + } + public Optional getDisplayParent(DataStoreEntry entry) { if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { return Optional.empty(); @@ -236,70 +303,49 @@ public abstract class DataStorage { try { var provider = entry.getProvider(); - return Optional.ofNullable(provider.getDisplayParent(entry)).filter(dataStoreEntry -> storeEntries.contains(dataStoreEntry)); + return Optional.ofNullable(provider.getDisplayParent(entry)) + .filter(dataStoreEntry -> storeEntries.contains(dataStoreEntry)); } catch (Exception ex) { return Optional.empty(); } } - public List getStoreChildren(DataStoreEntry entry, boolean deep) { + public Set getDeepStoreChildren(DataStoreEntry entry) { + var set = new HashSet(); + getStoreChildren(entry).forEach(entry1 -> { + set.addAll(getDeepStoreChildren(entry1)); + }); + return set; + } + + public Set getStoreChildren(DataStoreEntry entry) { if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { - return List.of(); + return Set.of(); } var entries = getStoreEntries(); if (!entries.contains(entry)) { - return List.of(); + return Set.of(); } if (entry.getChildrenCache() != null) { return entry.getChildrenCache(); } - var children = new ArrayList<>(entries.stream() + var children = entries.stream() .filter(other -> { if (other.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { return false; } var parent = getDisplayParent(other); - return parent.isPresent() - && parent.get().equals(entry); + return parent.isPresent() && parent.get().equals(entry); }) - .toList()); + .collect(Collectors.toSet()); entry.setChildrenCache(children); - - if (deep) { - for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) { - children.addAll(getStoreChildren(dataStoreEntry, true)); - } - } - return children; } - public abstract Path getInternalStreamPath(@NonNull UUID uuid); - - private void checkImmutable() { - if (System.getProperty(IMMUTABLE_PROP) != null) { - if (Boolean.parseBoolean(System.getProperty(IMMUTABLE_PROP))) { - throw new IllegalStateException("Storage is immutable"); - } - } - } - - protected Path getStoresDir() { - return dir.resolve("stores"); - } - - protected Path getStreamsDir() { - return dir.resolve("streams"); - } - - protected Path getCategoriesDir() { - return dir.resolve("categories"); - } - public List getUsableStores() { return new ArrayList<>(getStoreEntries().stream() .filter(entry -> entry.getValidity().isUsable()) @@ -307,17 +353,6 @@ public abstract class DataStorage { .toList()); } - public DataStoreEntry getStoreEntry(@NonNull String name, boolean acceptDisabled) { - var entry = storeEntries.stream() - .filter(n -> n.getName().equalsIgnoreCase(name)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Store with name " + name + " not found")); - if (!acceptDisabled && entry.isDisabled()) { - throw new IllegalArgumentException("Store with name " + name + " is disabled"); - } - return entry; - } - public DataStoreId getId(DataStoreEntry entry) { var names = new ArrayList(); names.add(entry.getName().replaceAll(":", "_")); @@ -338,7 +373,7 @@ public abstract class DataStorage { var current = getStoreEntryIfPresent(id.getNames().get(0)); if (current.isPresent()) { for (int i = 1; i < id.getNames().size(); i++) { - var children = getStoreChildren(current.get(), false); + var children = getStoreChildren(current.get()); int finalI = i; current = children.stream() .filter(dataStoreEntry -> dataStoreEntry @@ -358,26 +393,24 @@ public abstract class DataStorage { } public DataStoreEntry getStoreEntry(@NonNull DataStore store) { - return getStoreEntryIfPresent(store) - .orElseThrow(() -> new IllegalArgumentException("Store not found")); + return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found")); } public Optional getStoreEntryIfPresent(@NonNull DataStore store) { return storeEntries.stream() - .filter(n -> n.getStore() == store || (n.getStore() != null - && Objects.equals(store.getClass(), n.getStore().getClass()) - && store.equals(n.getStore()))) + .filter(n -> n.getStore() == store + || (n.getStore() != null + && Objects.equals(store.getClass(), n.getStore().getClass()) + && store.equals(n.getStore()))) .findFirst(); } - public abstract boolean supportsSharing(); - public DataStoreCategory getRootCategory(DataStoreCategory category) { DataStoreCategory last = category; DataStoreCategory p = category; while ((p = DataStorage.get() - .getStoreCategoryIfPresent(p.getParentCategory()) - .orElse(null)) + .getStoreCategoryIfPresent(p.getParentCategory()) + .orElse(null)) != null) { last = p; } @@ -402,61 +435,6 @@ public abstract class DataStorage { .findFirst(); } - public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) { - var oldParent = DataStorage.get().getDisplayParent(entry); - var newParent = DataStorage.get().getDisplayParent(newEntry); - var diffParent = Objects.equals(oldParent, newParent); - - propagateUpdate( - () -> { - newEntry.finalizeEntry(); - - var children = getStoreChildren(entry, true); - if (!diffParent) { - var toRemove = Stream.concat(Stream.of(entry), children.stream()) - .toArray(DataStoreEntry[]::new); - listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove)); - } - - entry.applyChanges(newEntry); - entry.initializeEntry(); - - if (!diffParent) { - var toAdd = Stream.concat(Stream.of(entry), children.stream()) - .toArray(DataStoreEntry[]::new); - listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); - refreshValidities(true); - } - }, - entry); - } - - public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) { - propagateUpdate( - () -> { - var children = getStoreChildren(entry, true); - var toRemove = - Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); - listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove)); - - entry.setCategoryUuid(newCategory.getUuid()); - children.forEach(child -> child.setCategoryUuid(newCategory.getUuid())); - - var toAdd = - Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); - listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); - }, - entry); - } - - void propagateUpdate(FailableRunnable runnable, DataStoreEntry origin) throws T { - var children = getStoreChildren(origin, true); - runnable.run(); - children.forEach(entry -> { - entry.refresh(); - }); - } - public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) { if (storeCategories.contains(cat)) { return cat; @@ -489,17 +467,21 @@ public abstract class DataStorage { return byId; } - if (e.getValidity().isUsable()) { - var displayParent = e.getProvider().getDisplayParent(e); - if (displayParent != null) { - displayParent.setExpanded(true); - addStoreEntryIfNotPresent(displayParent); - displayParent.setChildrenCache(null); - } + var syntheticParent = getSyntheticParent(e); + if (syntheticParent.isPresent()) { + addStoreEntryIfNotPresent(syntheticParent.get()); + } + + var displayParent = syntheticParent.or(() -> getDisplayParent(e)); + if (displayParent.isPresent()) { + displayParent.get().setExpanded(true); } e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); this.storeEntries.add(e); + displayParent.ifPresent(p -> { + p.setChildrenCache(null); + }); saveAsync(); this.listeners.forEach(l -> l.onStoreAdd(e)); @@ -508,13 +490,14 @@ public abstract class DataStorage { return e; } - public DataStoreEntry getOrCreateNewEntry(String name, DataStore store) { - var found = getStoreEntryIfPresent(store); + public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) { + var uuid = UuidHelper.generateFromObject(parent.getUuid(), name); + var found = getStoreEntryIfPresent(uuid); if (found.isPresent()) { return found.get(); } - return DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store); + return DataStoreEntry.createNew(uuid, parent.getCategoryUuid(), name, store); } public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) { @@ -523,13 +506,21 @@ public abstract class DataStorage { return; } - var displayParent = e.getProvider().getDisplayParent(e); - if (displayParent != null) { - addStoreEntryIfNotPresent(displayParent); + var syntheticParent = getSyntheticParent(e); + if (syntheticParent.isPresent()) { + addStoreEntryIfNotPresent(syntheticParent.get()); + } + + var displayParent = syntheticParent.or(() -> getDisplayParent(e)); + if (displayParent.isPresent()) { + displayParent.get().setExpanded(true); } e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); this.storeEntries.add(e); + displayParent.ifPresent(p -> { + p.setChildrenCache(null); + }); } this.listeners.forEach(l -> l.onStoreAdd(es)); for (DataStoreEntry e : es) { @@ -567,12 +558,8 @@ public abstract class DataStorage { } public void deleteStoreEntry(@NonNull DataStoreEntry store) { - propagateUpdate( - () -> { - store.finalizeEntry(); - this.storeEntries.remove(store); - }, - store); + store.finalizeEntry(); + this.storeEntries.remove(store); this.listeners.forEach(l -> l.onStoreRemove(store)); refreshValidities(false); saveAsync(); @@ -594,19 +581,6 @@ public abstract class DataStorage { this.listeners.forEach(l -> l.onCategoryRemove(cat)); } - public void addListener(StorageListener l) { - this.listeners.add(l); - } - - public abstract void load(); - - public void saveAsync() { - // TODO: Don't make this a daemon thread to guarantee proper saving - ThreadHelper.unstarted(this::save).start(); - } - - public abstract void save(); - public Optional getStoreEntryIfPresent(UUID id) { return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); } @@ -619,11 +593,11 @@ public abstract class DataStorage { return getStoreEntryIfPresent(LOCAL_ID).orElse(null); } - public List getStoreEntries() { - return new ArrayList<>(storeEntries); + public Set getStoreEntries() { + return storeEntries; } public List getStoreCategories() { - return new ArrayList<>(storeCategories); + return storeCategories; } } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 37dc155a0..bbfa501cf 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -20,9 +20,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.*; +import java.util.stream.Collectors; @Value -@EqualsAndHashCode(callSuper = true) public class DataStoreEntry extends StorageElement { @NonFinal @@ -68,7 +68,7 @@ public class DataStoreEntry extends StorageElement { @NonFinal @Setter - List childrenCache = null; + Set childrenCache = null; private DataStoreEntry( Path directory, @@ -98,6 +98,21 @@ public class DataStoreEntry extends StorageElement { this.storePersistentStateNode = storePersistentState; } + @Override + public boolean equals(Object o) { + return o == this || (o instanceof DataStoreEntry e && e.getUuid().equals(getUuid())); + } + + @Override + public int hashCode() { + return getUuid().hashCode(); + } + + @Override + public String toString() { + return getName(); + } + public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) { return createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, store); } @@ -351,7 +366,7 @@ public class DataStoreEntry extends StorageElement { if (store instanceof ValidatableStore l) { l.validate(); } else if (store instanceof FixedHierarchyStore h) { - childrenCache = h.listChildren(this).stream().map(DataStoreEntryRef::get).toList(); + childrenCache = h.listChildren(this).stream().map(DataStoreEntryRef::get).collect(Collectors.toSet()); } } finally { setInRefresh(false); 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 9665cd203..29ed7a100 100644 --- a/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java @@ -2,12 +2,9 @@ package io.xpipe.app.storage; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; -import lombok.NonNull; import org.apache.commons.io.FileUtils; import java.nio.file.Files; -import java.nio.file.Path; -import java.util.UUID; public class ImpersistentStorage extends DataStorage { @@ -34,8 +31,4 @@ public class ImpersistentStorage extends DataStorage { } } - @Override - public Path getInternalStreamPath(@NonNull UUID uuid) { - return FileUtils.getTempDirectory().toPath().resolve(uuid.toString()); - } } 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 1a5af5861..8e0ad0db9 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -8,7 +8,6 @@ import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.XPipeSession; import io.xpipe.core.store.LocalStore; import lombok.Getter; -import lombok.NonNull; import org.apache.commons.io.FileUtils; import java.io.IOException; @@ -270,6 +269,13 @@ public class StandardStorage extends DataStorage { // Refresh to update state storeEntries.forEach(dataStoreEntry -> dataStoreEntry.refresh()); + storeEntries.forEach(entry -> { + var syntheticParent = getSyntheticParent(entry); + syntheticParent.ifPresent(entry1 -> { + addStoreEntryIfNotPresent(entry1); + }); + }); + refreshValidities(true); deleteLeftovers(); @@ -330,11 +336,6 @@ public class StandardStorage extends DataStorage { gitStorageHandler.postSave(); } - @Override - public Path getInternalStreamPath(@NonNull UUID uuid) { - return getStreamsDir().resolve(uuid.toString()); - } - @Override public boolean supportsSharing() { return gitStorageHandler.supportsShare(); diff --git a/app/src/main/resources/io/xpipe/app/resources/style/application-choice-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/application-choice-comp.css deleted file mode 100644 index a3f8e5454..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/application-choice-comp.css +++ /dev/null @@ -1,15 +0,0 @@ -.application-choice-comp { --fx-border-width: 1px; --fx-border-color: #073B4C43; --fx-background-color: #118AB210; -} - -.application-choice-comp .combo-box > .list-cell { - -fx-padding: 0 0 0 0; - -fx-border-insets: 0 0 0 0; -} - -.application-choice-comp .combo-box > .list-cell .icon { - -fx-fit-width: 1em; - -fx-fit-height: 1em; -} \ No newline at end of file diff --git a/app/src/main/resources/io/xpipe/app/resources/style/char-choice-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/char-choice-comp.css deleted file mode 100644 index 88ed47571..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/char-choice-comp.css +++ /dev/null @@ -1,18 +0,0 @@ -.char-choice-comp .text-field { --fx-border-width: 1px; --fx-border-radius: 4px; --fx-border-color: -xp-border; --fx-background-color: -xp-base; --fx-pref-width: 1.7em; --fx-display-caret: false; --fx-padding: 0.3em 0 0.3em 0; --fx-alignment: center; -} - -.char-choice-comp .text-field:focused { --fx-border-color: -xp-border-highlight; -} - -.char-choice-comp { --fx-spacing: 0.3em; -} \ No newline at end of file diff --git a/app/src/main/resources/io/xpipe/app/resources/style/data-source-config.css b/app/src/main/resources/io/xpipe/app/resources/style/data-source-config.css deleted file mode 100644 index 9a450933f..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/data-source-config.css +++ /dev/null @@ -1,4 +0,0 @@ -.data-source-config { --fx-padding: 1.5em; --fx-spacing: 1em; -} diff --git a/app/src/main/resources/io/xpipe/app/resources/style/data-source-dialog.css b/app/src/main/resources/io/xpipe/app/resources/style/data-source-dialog.css deleted file mode 100644 index d4aacdd58..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/data-source-dialog.css +++ /dev/null @@ -1,43 +0,0 @@ - -.data-source-finish-step .store-options { - -fx-spacing: 0.5em; -} - -.data-source-finish-step .data-source-id { - -fx-padding: 0.75em; - -fx-spacing: 0.5em; -} - -.data-source-finish-step .storage-group-selector { --fx-border-width: 0; --fx-background-color: -xp-base; -} - -.data-source-finish-step .data-source-id .input-line { --fx-opacity: 0.2; -} - - - -.data-source-save-step { - -fx-padding: 1.5em; - -fx-spacing: 1.0em; -} - -.data-source-save-step .store-options { - -fx-spacing: 0.5em; -} - -.data-source-save-step .data-source-id { - -fx-padding: 0.75em; - -fx-spacing: 0.5em; -} - -.data-source-save-step .storage-group-selector { --fx-border-width: 0; --fx-background-color: -xp-base; -} - -.data-source-save-step .data-source-id .input-line { --fx-opacity: 0.2; -} diff --git a/app/src/main/resources/io/xpipe/app/resources/style/data-source-preview.css b/app/src/main/resources/io/xpipe/app/resources/style/data-source-preview.css deleted file mode 100644 index 7184643ef..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/data-source-preview.css +++ /dev/null @@ -1,23 +0,0 @@ -.data-source-preview { --fx-border-width: 1px; --fx-border-color: -xp-border; --fx-border-radius: 4px; --fx-padding: 0.4em; --fx-spacing: 8; -} - -.data-source-preview .data-source-type-comp { --fx-padding: 0.3em; -} - -.data-source-preview .vertical-comp { --fx-spacing: 8; -} - -.data-source-preview .jfx-text-field .input-line { --fx-opacity: 0.6; -} - -.data-source-preview .jfx-text-field { --fx-padding: 0 0 0 0; -} \ No newline at end of file diff --git a/app/src/main/resources/io/xpipe/app/resources/style/data-source-retrieve.css b/app/src/main/resources/io/xpipe/app/resources/style/data-source-retrieve.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/resources/io/xpipe/app/resources/style/data-source-type-choice-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/data-source-type-choice-comp.css deleted file mode 100644 index 092d2da2a..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/data-source-type-choice-comp.css +++ /dev/null @@ -1,3 +0,0 @@ -.data-source-type { --fx-padding: 0.15em 0 0.15em 0.05em; -} diff --git a/app/src/main/resources/io/xpipe/app/resources/style/table-mapping.css b/app/src/main/resources/io/xpipe/app/resources/style/table-mapping.css deleted file mode 100644 index baf1ca0b0..000000000 --- a/app/src/main/resources/io/xpipe/app/resources/style/table-mapping.css +++ /dev/null @@ -1,22 +0,0 @@ -.table-mapping-comp { --fx-hgap: 1em; --fx-vgap: .3em; --fx-padding: 0.5em; -} - -.table-mapping-comp .odd { --fx-text-fill: grey; -} - -.table-mapping-confirmation-comp { --fx-spacing: 1em; -} - -.table-mapping-confirmation-comp .grid-container { --fx-border-width: 1px; --fx-border-color:-color-accent-fg; --fx-background-color: transparent; --fx-border-radius: 4px; --fx-padding: 2px; --fx-background-radius: 4px; -} \ No newline at end of file diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java index c8c95d90c..ecda4b865 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java @@ -1,9 +1,6 @@ package io.xpipe.beacon; import io.xpipe.beacon.exchange.WriteStreamExchange; -import io.xpipe.beacon.exchange.cli.StoreAddExchange; -import io.xpipe.beacon.util.QuietDialogHandler; -import io.xpipe.core.store.InternalStreamStore; import io.xpipe.core.util.FailableBiConsumer; import io.xpipe.core.util.FailableConsumer; import lombok.Getter; @@ -174,25 +171,6 @@ public abstract class BeaconConnection implements AutoCloseable { } } - public InternalStreamStore createInternalStreamStore() { - return createInternalStreamStore(null); - } - - public InternalStreamStore createInternalStreamStore(String name) { - var store = new InternalStreamStore(); - var addReq = StoreAddExchange.Request.builder() - .storeInput(store) - .name(name != null ? name : store.getUuid().toString()) - .build(); - StoreAddExchange.Response addRes = performSimpleExchange(addReq); - QuietDialogHandler.handle(addRes.getConfig(), this); - return store; - } - - public void writeStream(InternalStreamStore s, InputStream in) { - writeStream(s.getUuid().toString(), in); - } - public void writeStream(String name, InputStream in) { performOutputExchange(WriteStreamExchange.Request.builder().name(name).build(), in::transferTo); } diff --git a/core/src/main/java/io/xpipe/core/store/InternalStreamStore.java b/core/src/main/java/io/xpipe/core/store/InternalStreamStore.java deleted file mode 100644 index 7e1194757..000000000 --- a/core/src/main/java/io/xpipe/core/store/InternalStreamStore.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.xpipe.core.store; - -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.xpipe.core.util.DataStateProvider; -import io.xpipe.core.util.JacksonizedValue; -import lombok.Getter; -import lombok.experimental.SuperBuilder; -import lombok.extern.jackson.Jacksonized; - -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.UUID; - -@JsonTypeName("internalStream") -@SuperBuilder -@Jacksonized -@Getter -public class InternalStreamStore extends JacksonizedValue implements StreamDataStore { - - private final UUID uuid; - - public InternalStreamStore() { - this.uuid = UUID.randomUUID(); - } - - private Path getFile() { - return DataStateProvider.get().getInternalStreamStore(uuid); - } - - @Override - public InputStream openInput() throws Exception { - return Files.newInputStream(getFile()); - } - - @Override - public OutputStream openOutput() throws Exception { - return Files.newOutputStream(getFile()); - } -} diff --git a/core/src/main/java/io/xpipe/core/util/DataStateProvider.java b/core/src/main/java/io/xpipe/core/util/DataStateProvider.java index 078de86d8..cf2680d7b 100644 --- a/core/src/main/java/io/xpipe/core/util/DataStateProvider.java +++ b/core/src/main/java/io/xpipe/core/util/DataStateProvider.java @@ -3,9 +3,7 @@ package io.xpipe.core.util; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStoreState; -import java.nio.file.Path; import java.util.ServiceLoader; -import java.util.UUID; import java.util.function.Supplier; public abstract class DataStateProvider { @@ -31,6 +29,4 @@ public abstract class DataStateProvider { public abstract T getCache(DataStore store, String key, Class c, Supplier def); public abstract boolean isInStorage(DataStore store); - - public abstract Path getInternalStreamStore(UUID id); } diff --git a/core/src/main/java/io/xpipe/core/util/UuidHelper.java b/core/src/main/java/io/xpipe/core/util/UuidHelper.java index 2457aa570..b9e2f1b9b 100644 --- a/core/src/main/java/io/xpipe/core/util/UuidHelper.java +++ b/core/src/main/java/io/xpipe/core/util/UuidHelper.java @@ -1,13 +1,14 @@ package io.xpipe.core.util; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Optional; import java.util.UUID; public class UuidHelper { - public static UUID generateFromObject(Object o) { - return UUID.nameUUIDFromBytes(o.toString().getBytes(StandardCharsets.UTF_8)); + public static UUID generateFromObject(Object... o) { + return UUID.nameUUIDFromBytes(Arrays.toString(o).getBytes(StandardCharsets.UTF_8)); } public static Optional parse(String s) { diff --git a/dist/jpackage.gradle b/dist/jpackage.gradle index 9f8dfd208..fea8ce0b8 100644 --- a/dist/jpackage.gradle +++ b/dist/jpackage.gradle @@ -2,7 +2,7 @@ import java.util.stream.Collectors def distDir = "${project.layout.buildDirectory.get()}/dist" -def distJvmArgs = new ArrayList(project(':app').application.applicationDefaultJvmArgs) +def distJvmArgs = new ArrayList(project(':app').jvmRunArgs) def releaseArguments = distJvmArgs + [ "-Dio.xpipe.app.version=$rootProject.versionString", diff --git a/ext/base/src/main/java/io/xpipe/ext/base/InternalStreamProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/InternalStreamProvider.java deleted file mode 100644 index 095ac087f..000000000 --- a/ext/base/src/main/java/io/xpipe/ext/base/InternalStreamProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.xpipe.ext.base; - -import io.xpipe.app.ext.DataStoreProvider; -import io.xpipe.core.store.InternalStreamStore; -import io.xpipe.core.store.DataStore; - -import java.util.List; - -public class InternalStreamProvider implements DataStoreProvider { - - @Override - public DataStore defaultStore() { - return new InternalStreamStore(); - } - - @Override - public List getPossibleNames() { - return List.of("internalStream"); - } - - @Override - public List> getStoreClasses() { - return List.of(InternalStreamStore.class); - } -} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java index cbe120da3..ad9fd1cdc 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java @@ -24,7 +24,7 @@ public class DeleteStoreChildrenAction implements ActionProvider { @Override public void execute() { - DataStorage.get().deleteChildren(store, true); + DataStorage.get().deleteChildren(store); } } @@ -50,7 +50,7 @@ public class DeleteStoreChildrenAction implements ActionProvider { @Override public boolean isApplicable(DataStoreEntryRef o) { return !(o.getStore() instanceof FixedHierarchyStore) && DataStorage.get() - .getStoreChildren(o.get(), true) + .getStoreChildren(o.get()) .size() > 1; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java index 244f1488d..8849aca51 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java @@ -33,7 +33,7 @@ public class RefreshStoreAction implements ActionProvider { @Override public boolean isApplicable(DataStoreEntryRef o) { - return DataStorage.get().getStoreChildren(o.get(), true).size() == 0; + return DataStorage.get().getStoreChildren(o.get()).size() == 0; } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStore.java index 3f08e0af8..72c2e8ef8 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStore.java @@ -33,7 +33,7 @@ public class ScriptGroupStore extends ScriptStore implements GroupStore> getEffectiveScripts() { var self = getSelfEntry(); - return DataStorage.get().getStoreChildren(self, true).stream() + return DataStorage.get().getDeepStoreChildren(self).stream() .map(dataStoreEntry -> dataStoreEntry.ref()) .toList(); } diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index bf5d9d453..6a9e53471 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -2,7 +2,6 @@ import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.ext.base.InMemoryStoreProvider; -import io.xpipe.ext.base.InternalStreamProvider; import io.xpipe.ext.base.action.*; import io.xpipe.ext.base.browser.*; import io.xpipe.ext.base.script.ScriptGroupStoreProvider; @@ -60,6 +59,5 @@ open module io.xpipe.ext.base { provides DataStoreProvider with ScriptGroupStoreProvider, SimpleScriptStoreProvider, - InternalStreamProvider, InMemoryStoreProvider; } diff --git a/gradle/gradle_scripts/javafx.gradle b/gradle/gradle_scripts/javafx.gradle index 017cdaf30..13d63a926 100644 --- a/gradle/gradle_scripts/javafx.gradle +++ b/gradle/gradle_scripts/javafx.gradle @@ -19,11 +19,13 @@ configurations { dep } +def jfxVersion = '22-ea+11' + dependencies { - dep "org.openjfx:javafx-base:21:${platform}" - dep "org.openjfx:javafx-controls:21:${platform}" - dep "org.openjfx:javafx-graphics:21:${platform}" - dep "org.openjfx:javafx-media:21:${platform}" - dep "org.openjfx:javafx-web:21:${platform}" - dep "org.openjfx:javafx-swing:21:${platform}" + dep "org.openjfx:javafx-base:${jfxVersion}:${platform}" + dep "org.openjfx:javafx-controls:${jfxVersion}:${platform}" + dep "org.openjfx:javafx-graphics:${jfxVersion}:${platform}" + dep "org.openjfx:javafx-media:${jfxVersion}:${platform}" + dep "org.openjfx:javafx-web:${jfxVersion}:${platform}" + dep "org.openjfx:javafx-swing:${jfxVersion}:${platform}" }