Performance improvements

This commit is contained in:
crschnick 2023-10-16 03:37:22 +00:00
parent b9ebce0771
commit c80a31bffe
37 changed files with 319 additions and 570 deletions

View file

@ -89,33 +89,33 @@ project.allExtensions.forEach((Project p) -> {
} }
}) })
project.ext {
List<String> jvmRunArgs = [ jvmRunArgs = [
"--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix", "--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix",
"--add-exports", "javafx.graphics/com.sun.javafx.stage=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.binding=com.jfoenix",
"--add-exports", "javafx.base/com.sun.javafx.event=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=com.jfoenix",
"--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=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.traversal=org.controlsfx.controls",
"--add-exports", "javafx.graphics/com.sun.javafx.scene=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-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=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.reflect=com.jfoenix", "--add-opens", "java.base/java.lang.reflect=com.jfoenix",
"--add-opens", "java.base/java.lang=io.xpipe.core", "--add-opens", "java.base/java.lang=io.xpipe.core",
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app", "--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
"--add-opens", "java.desktop/java.awt=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", "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.view=io.xpipe.app',
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app', "--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app',
"-Xmx8g", "-Xmx8g",
"-Dio.xpipe.app.arch=$rootProject.arch", "-Dio.xpipe.app.arch=$rootProject.arch",
// "-XX:+ExitOnOutOfMemoryError", "-Dfile.encoding=UTF-8",
"-Dfile.encoding=UTF-8", '-XX:+UseZGC',
'-XX:+UseZGC', "-Dvisualvm.display.name=XPipe"
"-Dvisualvm.display.name=XPipe" ]
] }
import org.gradle.internal.os.OperatingSystem import org.gradle.internal.os.OperatingSystem
@ -133,6 +133,7 @@ application {
mainModule = 'io.xpipe.app' mainModule = 'io.xpipe.app'
mainClass = 'io.xpipe.app.Main' mainClass = 'io.xpipe.app.Main'
applicationDefaultJvmArgs = jvmRunArgs applicationDefaultJvmArgs = jvmRunArgs
applicationDefaultJvmArgs.add('-XX:+EnableDynamicAgentLoading')
} }
run { 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", "-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" "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
) )
jvmArgs += '-XX:+EnableDynamicAgentLoading'
systemProperties run.systemProperties systemProperties run.systemProperties
} }

View file

@ -21,7 +21,7 @@ import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
@ -136,7 +136,7 @@ public class BrowserComp extends SimpleComp {
} }
private Node createTabs() { private Node createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableBooleanValue>of( var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()), Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())), BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)), new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp; package io.xpipe.app.comp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.base.SideMenuBarComp; import io.xpipe.app.comp.base.SideMenuBarComp;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppLayoutModel; 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.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import javafx.beans.binding.Bindings;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import java.util.HashMap; import java.util.stream.Collectors;
import java.util.Map;
public class AppLayoutComp extends Comp<CompStructure<Pane>> { public class AppLayoutComp extends Comp<CompStructure<Pane>> {
@ -21,35 +21,26 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
@Override @Override
public CompStructure<Pane> createBase() { public CompStructure<Pane> createBase() {
var map = new HashMap<AppLayoutModel.Entry, Region>(); var multi = new MultiContentComp(model.getEntries().stream()
getRegion(model.getEntries().get(0), map); .collect(Collectors.toMap(
getRegion(model.getEntries().get(1), map); entry -> entry.comp(),
entry -> PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
return model.getSelected().getValue().equals(entry);
},
model.getSelected())))));
var pane = new BorderPane(); var pane = new BorderPane();
var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries()); var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries());
pane.setCenter(getRegion(model.getSelected().getValue(), map)); pane.setCenter(multi.createRegion());
pane.setRight(sidebar.createRegion()); pane.setRight(sidebar.createRegion());
pane.getStyleClass().add("background"); pane.getStyleClass().add("background");
model.getSelected().addListener((c, o, n) -> { model.getSelected().addListener((c, o, n) -> {
if (o != null && o.equals(model.getEntries().get(2))) { if (o != null && o.equals(model.getEntries().get(2))) {
AppPrefs.get().save(); AppPrefs.get().save();
} }
PlatformThread.runLaterIfNeeded(() -> {
pane.setCenter(getRegion(n, map));
});
}); });
AppFont.normal(pane); AppFont.normal(pane);
return new SimpleCompStructure<>(pane); return new SimpleCompStructure<>(pane);
} }
private Region getRegion(AppLayoutModel.Entry entry, Map<AppLayoutModel.Entry, Region> map) {
if (map.containsKey(entry)) {
return map.get(entry);
}
Region r = entry.comp().createRegion();
map.put(entry, r);
return r;
}
} }

View file

@ -4,7 +4,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener; 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.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
@ -12,9 +12,9 @@ import java.util.Map;
public class MultiContentComp extends SimpleComp { public class MultiContentComp extends SimpleComp {
private final Map<Comp<?>, ObservableBooleanValue> content; private final Map<Comp<?>, ObservableValue<Boolean>> content;
public MultiContentComp(Map<Comp<?>, ObservableBooleanValue> content) { public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content) {
this.content = content; this.content = content;
} }
@ -22,7 +22,7 @@ public class MultiContentComp extends SimpleComp {
protected Region createSimple() { protected Region createSimple() {
var stack = new StackPane(); var stack = new StackPane();
stack.setPickOnBounds(false); stack.setPickOnBounds(false);
for (Map.Entry<Comp<?>, ObservableBooleanValue> entry : content.entrySet()) { for (Map.Entry<Comp<?>, ObservableValue<Boolean>> entry : content.entrySet()) {
var region = entry.getKey().createRegion(); var region = entry.getKey().createRegion();
stack.getChildren().add(region); stack.getChildren().add(region);
SimpleChangeListener.apply(PlatformThread.sync(entry.getValue()), val -> { SimpleChangeListener.apply(PlatformThread.sync(entry.getValue()), val -> {

View file

@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
@ -17,11 +17,15 @@ import java.util.List;
public class StoreEntryListComp extends SimpleComp { public class StoreEntryListComp extends SimpleComp {
private Comp<?> createList() { private Comp<?> createList() {
var topLevel = StoreViewState.get().getTopLevelSection(); var content = new ListBoxViewComp<>(
var content = new ListBoxViewComp<>(topLevel.getShownChildren(), topLevel.getAllChildren(), (StoreSection e) -> { StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
var custom = StoreSection.customSection(e, true).hgrow(); StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10))).styleClass("top"); (StoreSection e) -> {
}).apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0))); 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"); return content.styleClass("store-list-comp");
} }
@ -33,18 +37,19 @@ public class StoreEntryListComp extends SimpleComp {
return initialCount == StoreViewState.get().getAllEntries().size(); return initialCount == StoreViewState.get().getAllEntries().size();
}, },
StoreViewState.get().getAllEntries()); StoreViewState.get().getAllEntries());
var map = new LinkedHashMap<Comp<?>, ObservableBooleanValue>(); var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
map.put( map.put(
createList(), createList(),
BindingsHelper.persist( BindingsHelper.persist(Bindings.not(Bindings.isEmpty(
Bindings.not(Bindings.isEmpty(StoreViewState.get().getTopLevelSection().getShownChildren())))); StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
map.put(new StoreIntroComp(), showIntro); map.put(new StoreIntroComp(), showIntro);
map.put( map.put(
new StoreNotFoundComp(), new StoreNotFoundComp(),
BindingsHelper.persist(Bindings.and( BindingsHelper.persist(Bindings.and(
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())), Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
Bindings.isEmpty(StoreViewState.get().getTopLevelSection().getShownChildren())))); Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
return new MultiContentComp(map).createRegion(); return new MultiContentComp(map).createRegion();
} }
} }

View file

@ -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() { public boolean isInStorage() {
return DataStorage.get().getStoreEntries().contains(entry); return DataStorage.get().getStoreEntries().contains(entry);
} }
@ -92,7 +80,7 @@ public class StoreEntryWrapper {
public void delete() { public void delete() {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
DataStorage.get().deleteChildren(this.entry, true); DataStorage.get().deleteChildren(this.entry);
DataStorage.get().deleteStoreEntry(this.entry); DataStorage.get().deleteStoreEntry(this.entry);
}); });
} }

View file

@ -10,12 +10,10 @@ import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import lombok.Value; import lombok.Value;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@Value @Value
@ -33,7 +31,6 @@ public class StoreSection {
StoreEntryWrapper wrapper; StoreEntryWrapper wrapper;
ObservableList<StoreSection> allChildren; ObservableList<StoreSection> allChildren;
ObservableList<StoreSection> shownChildren; ObservableList<StoreSection> shownChildren;
ObservableList<StoreEntryWrapper> shownEntries;
int depth; int depth;
ObservableBooleanValue showDetails; ObservableBooleanValue showDetails;
@ -56,23 +53,14 @@ public class StoreSection {
} else { } else {
this.showDetails = new SimpleBooleanProperty(true); this.showDetails = new SimpleBooleanProperty(true);
} }
this.shownEntries = FXCollections.observableArrayList();
this.shownChildren.addListener((ListChangeListener<? super StoreSection>) c -> {
shownEntries.clear();
addShown(shownEntries);
});
}
private void addShown(List<StoreEntryWrapper> list) {
getShownChildren().forEach(shown -> {
list.add(shown.getWrapper());
shown.addShown(list);
});
} }
private static ObservableList<StoreSection> sorted( private static ObservableList<StoreSection> sorted(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) { ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
if (category == null) {
return list;
}
var c = Comparator.<StoreSection>comparingInt( var c = Comparator.<StoreSection>comparingInt(
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1); value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
var mapped = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode()); var mapped = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode());
@ -96,21 +84,26 @@ public class StoreSection {
Predicate<StoreEntryWrapper> entryFilter, Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString, ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) { ObservableValue<StoreCategoryWrapper> category) {
var cached = BindingsHelper.cachedMappedContentBinding(
all, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var topLevel = BindingsHelper.filteredContentBinding( 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, ordered,
section -> { section -> {
var sameCategory = var showFilter = filterString == null || section.shouldShow(filterString.get());
category.getValue().contains(section.getWrapper().getEntry());
var showFilter = section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter); 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, category,
filterString); filterString);
return new StoreSection(null, cached, topLevel, 0); return new StoreSection(null, ordered, shown, 0);
} }
private static StoreSection create( private static StoreSection create(
@ -125,10 +118,12 @@ public class StoreSection {
} }
var allChildren = BindingsHelper.filteredContentBinding(all, other -> { var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
return DataStorage.get() // if (true) return DataStorage.get()
.getDisplayParent(other.getEntry()) // .getDisplayParent(other.getEntry())
.map(found -> found.equals(e.getEntry())) // .map(found -> found.equals(e.getEntry()))
.orElse(false); // .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( var cached = BindingsHelper.cachedMappedContentBinding(
allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category)); allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
@ -136,7 +131,7 @@ public class StoreSection {
var filtered = BindingsHelper.filteredContentBinding( var filtered = BindingsHelper.filteredContentBinding(
ordered, ordered,
section -> { section -> {
return section.shouldShow(filterString.get()) return (filterString == null || section.shouldShow(filterString.get()))
&& section.anyMatches(entryFilter); && section.anyMatches(entryFilter);
}, },
category, category,

View file

@ -34,7 +34,7 @@ public class StoreViewState {
FXCollections.observableList(new CopyOnWriteArrayList<>()); FXCollections.observableList(new CopyOnWriteArrayList<>());
@Getter @Getter
private final StoreSection topLevelSection; private final StoreSection currentTopLevelSection;
@Getter @Getter
private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>(); private final Property<StoreCategoryWrapper> activeCategory = new SimpleObjectProperty<>();
@ -51,7 +51,7 @@ public class StoreViewState {
activeCategory.setValue(getAllConnectionsCategory()); activeCategory.setValue(getAllConnectionsCategory());
ErrorEvent.fromThrowable(exception).handle(); ErrorEvent.fromThrowable(exception).handle();
} }
topLevelSection = tl; currentTopLevelSection = tl;
} }
public ObservableList<StoreCategoryWrapper> getSortedCategories() { public ObservableList<StoreCategoryWrapper> getSortedCategories() {

View file

@ -20,6 +20,7 @@ public abstract class PlatformMode extends OperationMode {
@Override @Override
public void onSwitchTo() throws Throwable { public void onSwitchTo() throws Throwable {
if (App.getApp() != null) { if (App.getApp() != null) {
StoreViewState.init();
return; return;
} }

View file

@ -5,13 +5,14 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException; import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.RemoveStoreExchange; import io.xpipe.beacon.exchange.cli.RemoveStoreExchange;
import io.xpipe.core.store.DataStoreId;
public class RemoveStoreExchangeImpl extends RemoveStoreExchange public class RemoveStoreExchangeImpl extends RemoveStoreExchange
implements MessageExchangeImpl<RemoveStoreExchange.Request, RemoveStoreExchange.Response> { implements MessageExchangeImpl<RemoveStoreExchange.Request, RemoveStoreExchange.Response> {
@Override @Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { 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()) { if (!s.getConfiguration().isDeletable()) {
throw new ClientException("Store is not deletable"); throw new ClientException("Store is not deletable");
} }

View file

@ -1,16 +1,17 @@
package io.xpipe.app.exchange.cli; package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl; import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.RenameStoreExchange; import io.xpipe.beacon.exchange.cli.RenameStoreExchange;
import io.xpipe.core.store.DataStoreId;
public class RenameStoreExchangeImpl extends RenameStoreExchange public class RenameStoreExchangeImpl extends RenameStoreExchange
implements MessageExchangeImpl<RenameStoreExchange.Request, RenameStoreExchange.Response> { implements MessageExchangeImpl<RenameStoreExchange.Request, RenameStoreExchange.Response> {
@Override @Override
public Response handleRequest(BeaconHandler handler, Request msg) { public Response handleRequest(BeaconHandler handler, Request msg) throws ClientException {
var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true); var s = getStoreEntryById(DataStoreId.fromString(msg.getStoreName()), true);
s.setName(msg.getNewName()); s.setName(msg.getNewName());
return Response.builder().build(); return Response.builder().build();
} }

View file

@ -98,6 +98,10 @@ public interface DataStoreProvider {
} }
default DataStoreEntry getDisplayParent(DataStoreEntry store) { default DataStoreEntry getDisplayParent(DataStoreEntry store) {
return getSyntheticParent(store);
}
default DataStoreEntry getSyntheticParent(DataStoreEntry store) {
return null; return null;
} }

View file

@ -239,14 +239,18 @@ public class BindingsHelper {
} }
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate, Observable... observables) { public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
return filteredContentBinding(l2, Bindings.createObjectBinding(() -> { return filteredContentBinding(
return new Predicate<>() { l2,
@Override Bindings.createObjectBinding(
public boolean test(V v) { () -> {
return predicate.test(v); return new Predicate<>() {
} @Override
}; public boolean test(V v) {
}, observables)); return predicate.test(v);
}
};
},
Arrays.stream(observables).filter( Objects::nonNull).toArray(Observable[]::new)));
} }
public static <V> ObservableList<V> filteredContentBinding( public static <V> ObservableList<V> filteredContentBinding(

View file

@ -1,12 +1,10 @@
package io.xpipe.app.storage; package io.xpipe.app.storage;
import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreState; import io.xpipe.core.store.DataStoreState;
import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.util.DataStateProvider; import io.xpipe.core.util.DataStateProvider;
import java.nio.file.Path;
import java.util.UUID;
import java.util.function.Supplier; import java.util.function.Supplier;
public class DataStateProviderImpl extends DataStateProvider { public class DataStateProviderImpl extends DataStateProvider {
@ -85,9 +83,4 @@ public class DataStateProviderImpl extends DataStateProvider {
var entry = DataStorage.get().getStoreEntryIfPresent(store); var entry = DataStorage.get().getStoreEntryIfPresent(store);
return entry.isPresent(); return entry.isPresent();
} }
@Override
public Path getInternalStreamStore(UUID id) {
return DataStorage.get().getInternalStreamPath(id);
}
} }

View file

@ -9,7 +9,7 @@ import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreId; import io.xpipe.core.store.DataStoreId;
import io.xpipe.core.store.FixedChildStore; import io.xpipe.core.store.FixedChildStore;
import io.xpipe.core.store.LocalStore; import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.FailableRunnable; import io.xpipe.core.util.UuidHelper;
import javafx.util.Pair; import javafx.util.Pair;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -17,8 +17,10 @@ import lombok.Setter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class DataStorage { public abstract class DataStorage {
@ -36,7 +38,7 @@ public abstract class DataStorage {
private static DataStorage INSTANCE; private static DataStorage INSTANCE;
protected final Path dir; protected final Path dir;
protected final List<DataStoreCategory> storeCategories; protected final List<DataStoreCategory> storeCategories;
protected final List<DataStoreEntry> storeEntries; protected final Set<DataStoreEntry> storeEntries;
@Getter @Getter
@Setter @Setter
@ -47,22 +49,10 @@ public abstract class DataStorage {
public DataStorage() { public DataStorage() {
this.dir = AppPrefs.get().storageDirectory().getValue(); this.dir = AppPrefs.get().storageDirectory().getValue();
this.storeEntries = new CopyOnWriteArrayList<>(); this.storeEntries = Collections.newSetFromMap(new ConcurrentHashMap<>());
this.storeCategories = new CopyOnWriteArrayList<>(); 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() { public DataStoreCategory getDefaultCategory() {
return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow(); return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow();
} }
@ -109,6 +99,80 @@ public abstract class DataStorage {
return INSTANCE; 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) { public boolean refreshChildren(DataStoreEntry e) {
if (!(e.getStore() instanceof FixedHierarchyStore)) { if (!(e.getStore() instanceof FixedHierarchyStore)) {
return false; return false;
@ -125,7 +189,9 @@ public abstract class DataStorage {
return false; 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() var toRemove = oldChildren.stream()
.filter(entry -> newChildren.stream() .filter(entry -> newChildren.stream()
.noneMatch( .noneMatch(
@ -139,7 +205,8 @@ public abstract class DataStorage {
var toUpdate = oldChildren.stream() var toUpdate = oldChildren.stream()
.map(entry -> { .map(entry -> {
var found = newChildren.stream() var found = newChildren.stream()
.filter(nc -> nc.getStore().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()) .filter(nc ->
nc.getStore().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId())
.findFirst() .findFirst()
.orElse(null); .orElse(null);
return new Pair<>(entry, found); return new Pair<>(entry, found);
@ -152,29 +219,21 @@ public abstract class DataStorage {
} }
deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new)); deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new));
addStoreEntriesIfNotPresent(toAdd.stream() addStoreEntriesIfNotPresent(toAdd.stream().map(DataStoreEntryRef::get).toArray(DataStoreEntry[]::new));
.map(DataStoreEntryRef::get)
.toArray(DataStoreEntry[]::new));
toUpdate.forEach(pair -> { toUpdate.forEach(pair -> {
propagateUpdate( pair.getKey().setStoreInternal(pair.getValue().getStore(), false);
() -> {
pair.getKey().setStoreInternal(pair.getValue().getStore(), false);
},
pair.getKey());
}); });
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).toList()); e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).collect(Collectors.toSet()));
saveAsync(); saveAsync();
return !newChildren.isEmpty(); return !newChildren.isEmpty();
} }
public void deleteWithChildren(DataStoreEntry... entries) { public void deleteWithChildren(DataStoreEntry... entries) {
var toDelete = Arrays.stream(entries) var toDelete = Arrays.stream(entries)
.flatMap(entry -> { .flatMap(entry -> {
// Reverse to delete deepest children first var c = getDeepStoreChildren(entry);
var ordered = getStoreChildren(entry, true); c.add(entry);
Collections.reverse(ordered); return c.stream();
ordered.add(entry);
return ordered.stream();
}) })
.toList(); .toList();
@ -185,22 +244,17 @@ public abstract class DataStorage {
saveAsync(); saveAsync();
} }
public void deleteChildren(DataStoreEntry e, boolean deep) { public void deleteChildren(DataStoreEntry e) {
// Reverse to delete deepest children first var c = getDeepStoreChildren(e);
var ordered = getStoreChildren(e, deep); c.forEach(entry -> entry.finalizeEntry());
Collections.reverse(ordered); this.storeEntries.removeAll(c);
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
ordered.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(ordered);
this.listeners.forEach(l -> l.onStoreRemove(ordered.toArray(DataStoreEntry[]::new)));
refreshValidities(false); refreshValidities(false);
saveAsync(); saveAsync();
} }
public boolean isRootEntry(DataStoreEntry entry) { public boolean isRootEntry(DataStoreEntry entry) {
var noParent = DataStorage.get() var noParent = DataStorage.get().getDisplayParent(entry).isEmpty();
.getDisplayParent(entry)
.isEmpty();
var diffParentCategory = DataStorage.get() var diffParentCategory = DataStorage.get()
.getDisplayParent(entry) .getDisplayParent(entry)
.map(p -> !p.getCategoryUuid().equals(entry.getCategoryUuid())) .map(p -> !p.getCategoryUuid().equals(entry.getCategoryUuid()))
@ -229,6 +283,19 @@ public abstract class DataStorage {
return current; return current;
} }
public Optional<DataStoreEntry> 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<DataStoreEntry> getDisplayParent(DataStoreEntry entry) { public Optional<DataStoreEntry> getDisplayParent(DataStoreEntry entry) {
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return Optional.empty(); return Optional.empty();
@ -236,70 +303,49 @@ public abstract class DataStorage {
try { try {
var provider = entry.getProvider(); 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) { } catch (Exception ex) {
return Optional.empty(); return Optional.empty();
} }
} }
public List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean deep) { public Set<DataStoreEntry> getDeepStoreChildren(DataStoreEntry entry) {
var set = new HashSet<DataStoreEntry>();
getStoreChildren(entry).forEach(entry1 -> {
set.addAll(getDeepStoreChildren(entry1));
});
return set;
}
public Set<DataStoreEntry> getStoreChildren(DataStoreEntry entry) {
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return List.of(); return Set.of();
} }
var entries = getStoreEntries(); var entries = getStoreEntries();
if (!entries.contains(entry)) { if (!entries.contains(entry)) {
return List.of(); return Set.of();
} }
if (entry.getChildrenCache() != null) { if (entry.getChildrenCache() != null) {
return entry.getChildrenCache(); return entry.getChildrenCache();
} }
var children = new ArrayList<>(entries.stream() var children = entries.stream()
.filter(other -> { .filter(other -> {
if (other.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { if (other.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return false; return false;
} }
var parent = getDisplayParent(other); var parent = getDisplayParent(other);
return parent.isPresent() return parent.isPresent() && parent.get().equals(entry);
&& parent.get().equals(entry);
}) })
.toList()); .collect(Collectors.toSet());
entry.setChildrenCache(children); entry.setChildrenCache(children);
if (deep) {
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
children.addAll(getStoreChildren(dataStoreEntry, true));
}
}
return children; 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<DataStore> getUsableStores() { public List<DataStore> getUsableStores() {
return new ArrayList<>(getStoreEntries().stream() return new ArrayList<>(getStoreEntries().stream()
.filter(entry -> entry.getValidity().isUsable()) .filter(entry -> entry.getValidity().isUsable())
@ -307,17 +353,6 @@ public abstract class DataStorage {
.toList()); .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) { public DataStoreId getId(DataStoreEntry entry) {
var names = new ArrayList<String>(); var names = new ArrayList<String>();
names.add(entry.getName().replaceAll(":", "_")); names.add(entry.getName().replaceAll(":", "_"));
@ -338,7 +373,7 @@ public abstract class DataStorage {
var current = getStoreEntryIfPresent(id.getNames().get(0)); var current = getStoreEntryIfPresent(id.getNames().get(0));
if (current.isPresent()) { if (current.isPresent()) {
for (int i = 1; i < id.getNames().size(); i++) { for (int i = 1; i < id.getNames().size(); i++) {
var children = getStoreChildren(current.get(), false); var children = getStoreChildren(current.get());
int finalI = i; int finalI = i;
current = children.stream() current = children.stream()
.filter(dataStoreEntry -> dataStoreEntry .filter(dataStoreEntry -> dataStoreEntry
@ -358,26 +393,24 @@ public abstract class DataStorage {
} }
public DataStoreEntry getStoreEntry(@NonNull DataStore store) { public DataStoreEntry getStoreEntry(@NonNull DataStore store) {
return getStoreEntryIfPresent(store) return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found"));
.orElseThrow(() -> new IllegalArgumentException("Store not found"));
} }
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) { public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntries.stream() return storeEntries.stream()
.filter(n -> n.getStore() == store || (n.getStore() != null .filter(n -> n.getStore() == store
&& Objects.equals(store.getClass(), n.getStore().getClass()) || (n.getStore() != null
&& store.equals(n.getStore()))) && Objects.equals(store.getClass(), n.getStore().getClass())
&& store.equals(n.getStore())))
.findFirst(); .findFirst();
} }
public abstract boolean supportsSharing();
public DataStoreCategory getRootCategory(DataStoreCategory category) { public DataStoreCategory getRootCategory(DataStoreCategory category) {
DataStoreCategory last = category; DataStoreCategory last = category;
DataStoreCategory p = category; DataStoreCategory p = category;
while ((p = DataStorage.get() while ((p = DataStorage.get()
.getStoreCategoryIfPresent(p.getParentCategory()) .getStoreCategoryIfPresent(p.getParentCategory())
.orElse(null)) .orElse(null))
!= null) { != null) {
last = p; last = p;
} }
@ -402,61 +435,6 @@ public abstract class DataStorage {
.findFirst(); .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);
}
<T extends Throwable> void propagateUpdate(FailableRunnable<T> runnable, DataStoreEntry origin) throws T {
var children = getStoreChildren(origin, true);
runnable.run();
children.forEach(entry -> {
entry.refresh();
});
}
public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) { public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) {
if (storeCategories.contains(cat)) { if (storeCategories.contains(cat)) {
return cat; return cat;
@ -489,17 +467,21 @@ public abstract class DataStorage {
return byId; return byId;
} }
if (e.getValidity().isUsable()) { var syntheticParent = getSyntheticParent(e);
var displayParent = e.getProvider().getDisplayParent(e); if (syntheticParent.isPresent()) {
if (displayParent != null) { addStoreEntryIfNotPresent(syntheticParent.get());
displayParent.setExpanded(true); }
addStoreEntryIfNotPresent(displayParent);
displayParent.setChildrenCache(null); var displayParent = syntheticParent.or(() -> getDisplayParent(e));
} if (displayParent.isPresent()) {
displayParent.get().setExpanded(true);
} }
e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e); this.storeEntries.add(e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
saveAsync(); saveAsync();
this.listeners.forEach(l -> l.onStoreAdd(e)); this.listeners.forEach(l -> l.onStoreAdd(e));
@ -508,13 +490,14 @@ public abstract class DataStorage {
return e; return e;
} }
public DataStoreEntry getOrCreateNewEntry(String name, DataStore store) { public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) {
var found = getStoreEntryIfPresent(store); var uuid = UuidHelper.generateFromObject(parent.getUuid(), name);
var found = getStoreEntryIfPresent(uuid);
if (found.isPresent()) { if (found.isPresent()) {
return found.get(); 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) { public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) {
@ -523,13 +506,21 @@ public abstract class DataStorage {
return; return;
} }
var displayParent = e.getProvider().getDisplayParent(e); var syntheticParent = getSyntheticParent(e);
if (displayParent != null) { if (syntheticParent.isPresent()) {
addStoreEntryIfNotPresent(displayParent); addStoreEntryIfNotPresent(syntheticParent.get());
}
var displayParent = syntheticParent.or(() -> getDisplayParent(e));
if (displayParent.isPresent()) {
displayParent.get().setExpanded(true);
} }
e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e); this.storeEntries.add(e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
} }
this.listeners.forEach(l -> l.onStoreAdd(es)); this.listeners.forEach(l -> l.onStoreAdd(es));
for (DataStoreEntry e : es) { for (DataStoreEntry e : es) {
@ -567,12 +558,8 @@ public abstract class DataStorage {
} }
public void deleteStoreEntry(@NonNull DataStoreEntry store) { public void deleteStoreEntry(@NonNull DataStoreEntry store) {
propagateUpdate( store.finalizeEntry();
() -> { this.storeEntries.remove(store);
store.finalizeEntry();
this.storeEntries.remove(store);
},
store);
this.listeners.forEach(l -> l.onStoreRemove(store)); this.listeners.forEach(l -> l.onStoreRemove(store));
refreshValidities(false); refreshValidities(false);
saveAsync(); saveAsync();
@ -594,19 +581,6 @@ public abstract class DataStorage {
this.listeners.forEach(l -> l.onCategoryRemove(cat)); 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<DataStoreEntry> getStoreEntryIfPresent(UUID id) { public Optional<DataStoreEntry> getStoreEntryIfPresent(UUID id) {
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
} }
@ -619,11 +593,11 @@ public abstract class DataStorage {
return getStoreEntryIfPresent(LOCAL_ID).orElse(null); return getStoreEntryIfPresent(LOCAL_ID).orElse(null);
} }
public List<DataStoreEntry> getStoreEntries() { public Set<DataStoreEntry> getStoreEntries() {
return new ArrayList<>(storeEntries); return storeEntries;
} }
public List<DataStoreCategory> getStoreCategories() { public List<DataStoreCategory> getStoreCategories() {
return new ArrayList<>(storeCategories); return storeCategories;
} }
} }

View file

@ -20,9 +20,9 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Value @Value
@EqualsAndHashCode(callSuper = true)
public class DataStoreEntry extends StorageElement { public class DataStoreEntry extends StorageElement {
@NonFinal @NonFinal
@ -68,7 +68,7 @@ public class DataStoreEntry extends StorageElement {
@NonFinal @NonFinal
@Setter @Setter
List<DataStoreEntry> childrenCache = null; Set<DataStoreEntry> childrenCache = null;
private DataStoreEntry( private DataStoreEntry(
Path directory, Path directory,
@ -98,6 +98,21 @@ public class DataStoreEntry extends StorageElement {
this.storePersistentStateNode = storePersistentState; 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) { public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
return createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, 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) { if (store instanceof ValidatableStore l) {
l.validate(); l.validate();
} else if (store instanceof FixedHierarchyStore h) { } 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 { } finally {
setInRefresh(false); setInRefresh(false);

View file

@ -2,12 +2,9 @@ package io.xpipe.app.storage;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import lombok.NonNull;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
public class ImpersistentStorage extends DataStorage { 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());
}
} }

View file

@ -8,7 +8,6 @@ import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.XPipeSession; import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore; import io.xpipe.core.store.LocalStore;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.io.IOException; import java.io.IOException;
@ -270,6 +269,13 @@ public class StandardStorage extends DataStorage {
// Refresh to update state // Refresh to update state
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.refresh()); storeEntries.forEach(dataStoreEntry -> dataStoreEntry.refresh());
storeEntries.forEach(entry -> {
var syntheticParent = getSyntheticParent(entry);
syntheticParent.ifPresent(entry1 -> {
addStoreEntryIfNotPresent(entry1);
});
});
refreshValidities(true); refreshValidities(true);
deleteLeftovers(); deleteLeftovers();
@ -330,11 +336,6 @@ public class StandardStorage extends DataStorage {
gitStorageHandler.postSave(); gitStorageHandler.postSave();
} }
@Override
public Path getInternalStreamPath(@NonNull UUID uuid) {
return getStreamsDir().resolve(uuid.toString());
}
@Override @Override
public boolean supportsSharing() { public boolean supportsSharing() {
return gitStorageHandler.supportsShare(); return gitStorageHandler.supportsShare();

View file

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

View file

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

View file

@ -1,4 +0,0 @@
.data-source-config {
-fx-padding: 1.5em;
-fx-spacing: 1em;
}

View file

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

View file

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

View file

@ -1,3 +0,0 @@
.data-source-type {
-fx-padding: 0.15em 0 0.15em 0.05em;
}

View file

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

View file

@ -1,9 +1,6 @@
package io.xpipe.beacon; package io.xpipe.beacon;
import io.xpipe.beacon.exchange.WriteStreamExchange; 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.FailableBiConsumer;
import io.xpipe.core.util.FailableConsumer; import io.xpipe.core.util.FailableConsumer;
import lombok.Getter; 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) { public void writeStream(String name, InputStream in) {
performOutputExchange(WriteStreamExchange.Request.builder().name(name).build(), in::transferTo); performOutputExchange(WriteStreamExchange.Request.builder().name(name).build(), in::transferTo);
} }

View file

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

View file

@ -3,9 +3,7 @@ package io.xpipe.core.util;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreState; import io.xpipe.core.store.DataStoreState;
import java.nio.file.Path;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.UUID;
import java.util.function.Supplier; import java.util.function.Supplier;
public abstract class DataStateProvider { public abstract class DataStateProvider {
@ -31,6 +29,4 @@ public abstract class DataStateProvider {
public abstract <T> T getCache(DataStore store, String key, Class<T> c, Supplier<T> def); public abstract <T> T getCache(DataStore store, String key, Class<T> c, Supplier<T> def);
public abstract boolean isInStorage(DataStore store); public abstract boolean isInStorage(DataStore store);
public abstract Path getInternalStreamStore(UUID id);
} }

View file

@ -1,13 +1,14 @@
package io.xpipe.core.util; package io.xpipe.core.util;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class UuidHelper { public class UuidHelper {
public static UUID generateFromObject(Object o) { public static UUID generateFromObject(Object... o) {
return UUID.nameUUIDFromBytes(o.toString().getBytes(StandardCharsets.UTF_8)); return UUID.nameUUIDFromBytes(Arrays.toString(o).getBytes(StandardCharsets.UTF_8));
} }
public static Optional<UUID> parse(String s) { public static Optional<UUID> parse(String s) {

View file

@ -2,7 +2,7 @@ import java.util.stream.Collectors
def distDir = "${project.layout.buildDirectory.get()}/dist" def distDir = "${project.layout.buildDirectory.get()}/dist"
def distJvmArgs = new ArrayList<String>(project(':app').application.applicationDefaultJvmArgs) def distJvmArgs = new ArrayList<String>(project(':app').jvmRunArgs)
def releaseArguments = distJvmArgs + [ def releaseArguments = distJvmArgs + [
"-Dio.xpipe.app.version=$rootProject.versionString", "-Dio.xpipe.app.version=$rootProject.versionString",

View file

@ -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<String> getPossibleNames() {
return List.of("internalStream");
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(InternalStreamStore.class);
}
}

View file

@ -24,7 +24,7 @@ public class DeleteStoreChildrenAction implements ActionProvider {
@Override @Override
public void execute() { public void execute() {
DataStorage.get().deleteChildren(store, true); DataStorage.get().deleteChildren(store);
} }
} }
@ -50,7 +50,7 @@ public class DeleteStoreChildrenAction implements ActionProvider {
@Override @Override
public boolean isApplicable(DataStoreEntryRef<DataStore> o) { public boolean isApplicable(DataStoreEntryRef<DataStore> o) {
return !(o.getStore() instanceof FixedHierarchyStore) && DataStorage.get() return !(o.getStore() instanceof FixedHierarchyStore) && DataStorage.get()
.getStoreChildren(o.get(), true) .getStoreChildren(o.get())
.size() .size()
> 1; > 1;
} }

View file

@ -33,7 +33,7 @@ public class RefreshStoreAction implements ActionProvider {
@Override @Override
public boolean isApplicable(DataStoreEntryRef<FixedHierarchyStore> o) { public boolean isApplicable(DataStoreEntryRef<FixedHierarchyStore> o) {
return DataStorage.get().getStoreChildren(o.get(), true).size() == 0; return DataStorage.get().getStoreChildren(o.get()).size() == 0;
} }
@Override @Override

View file

@ -33,7 +33,7 @@ public class ScriptGroupStore extends ScriptStore implements GroupStore<ScriptSt
@Override @Override
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() { public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
var self = getSelfEntry(); var self = getSelfEntry();
return DataStorage.get().getStoreChildren(self, true).stream() return DataStorage.get().getDeepStoreChildren(self).stream()
.map(dataStoreEntry -> dataStoreEntry.<ScriptStore>ref()) .map(dataStoreEntry -> dataStoreEntry.<ScriptStore>ref())
.toList(); .toList();
} }

View file

@ -2,7 +2,6 @@ import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.ext.base.InMemoryStoreProvider; import io.xpipe.ext.base.InMemoryStoreProvider;
import io.xpipe.ext.base.InternalStreamProvider;
import io.xpipe.ext.base.action.*; import io.xpipe.ext.base.action.*;
import io.xpipe.ext.base.browser.*; import io.xpipe.ext.base.browser.*;
import io.xpipe.ext.base.script.ScriptGroupStoreProvider; import io.xpipe.ext.base.script.ScriptGroupStoreProvider;
@ -60,6 +59,5 @@ open module io.xpipe.ext.base {
provides DataStoreProvider with provides DataStoreProvider with
ScriptGroupStoreProvider, ScriptGroupStoreProvider,
SimpleScriptStoreProvider, SimpleScriptStoreProvider,
InternalStreamProvider,
InMemoryStoreProvider; InMemoryStoreProvider;
} }

View file

@ -19,11 +19,13 @@ configurations {
dep dep
} }
def jfxVersion = '22-ea+11'
dependencies { dependencies {
dep "org.openjfx:javafx-base:21:${platform}" dep "org.openjfx:javafx-base:${jfxVersion}:${platform}"
dep "org.openjfx:javafx-controls:21:${platform}" dep "org.openjfx:javafx-controls:${jfxVersion}:${platform}"
dep "org.openjfx:javafx-graphics:21:${platform}" dep "org.openjfx:javafx-graphics:${jfxVersion}:${platform}"
dep "org.openjfx:javafx-media:21:${platform}" dep "org.openjfx:javafx-media:${jfxVersion}:${platform}"
dep "org.openjfx:javafx-web:21:${platform}" dep "org.openjfx:javafx-web:${jfxVersion}:${platform}"
dep "org.openjfx:javafx-swing:21:${platform}" dep "org.openjfx:javafx-swing:${jfxVersion}:${platform}"
} }