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,8 +89,8 @@ project.allExtensions.forEach((Project p) -> {
}
})
List<String> jvmRunArgs = [
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",
@ -111,11 +111,11 @@ List<String> jvmRunArgs = [
"--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"
]
}
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
}

View file

@ -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.<Comp<?>, ObservableBooleanValue>of(
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),

View file

@ -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<CompStructure<Pane>> {
@ -21,35 +21,26 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
@Override
public CompStructure<Pane> createBase() {
var map = new HashMap<AppLayoutModel.Entry, Region>();
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<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.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<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;
}
@ -22,7 +22,7 @@ public class MultiContentComp extends SimpleComp {
protected Region createSimple() {
var stack = new StackPane();
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();
stack.getChildren().add(region);
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.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 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 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<Comp<?>, ObservableBooleanValue>();
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
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();
}
}

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

View file

@ -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<StoreSection> allChildren;
ObservableList<StoreSection> shownChildren;
ObservableList<StoreEntryWrapper> 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<? 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(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
if (category == null) {
return list;
}
var c = Comparator.<StoreSection>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<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
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(
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,

View file

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

View file

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

View file

@ -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<RemoveStoreExchange.Request, RemoveStoreExchange.Response> {
@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");
}

View file

@ -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<RenameStoreExchange.Request, RenameStoreExchange.Response> {
@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();
}

View file

@ -98,6 +98,10 @@ public interface DataStoreProvider {
}
default DataStoreEntry getDisplayParent(DataStoreEntry store) {
return getSyntheticParent(store);
}
default DataStoreEntry getSyntheticParent(DataStoreEntry store) {
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) {
return filteredContentBinding(l2, Bindings.createObjectBinding(() -> {
return filteredContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(V v) {
return predicate.test(v);
}
};
}, observables));
},
Arrays.stream(observables).filter( Objects::nonNull).toArray(Observable[]::new)));
}
public static <V> ObservableList<V> filteredContentBinding(

View file

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

View file

@ -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<DataStoreCategory> storeCategories;
protected final List<DataStoreEntry> storeEntries;
protected final Set<DataStoreEntry> 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,17 +219,11 @@ 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());
});
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).toList());
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).collect(Collectors.toSet()));
saveAsync();
return !newChildren.isEmpty();
}
@ -170,11 +231,9 @@ public abstract class DataStorage {
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<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) {
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<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) {
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<DataStore> 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<String>();
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,20 +393,18 @@ 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<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntries.stream()
.filter(n -> n.getStore() == store || (n.getStore() != null
.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;
@ -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);
}
<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) {
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);
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<DataStoreEntry> 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<DataStoreEntry> getStoreEntries() {
return new ArrayList<>(storeEntries);
public Set<DataStoreEntry> getStoreEntries() {
return storeEntries;
}
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.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<DataStoreEntry> childrenCache = null;
Set<DataStoreEntry> 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);

View file

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

View file

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

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

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.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> T getCache(DataStore store, String key, Class<T> c, Supplier<T> def);
public abstract boolean isInStorage(DataStore store);
public abstract Path getInternalStreamStore(UUID id);
}

View file

@ -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<UUID> parse(String s) {

View file

@ -2,7 +2,7 @@ import java.util.stream.Collectors
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 + [
"-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
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<DataStore> o) {
return !(o.getStore() instanceof FixedHierarchyStore) && DataStorage.get()
.getStoreChildren(o.get(), true)
.getStoreChildren(o.get())
.size()
> 1;
}

View file

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

View file

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

View file

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