mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10:23 +00:00
Performance improvements
This commit is contained in:
parent
b9ebce0771
commit
c80a31bffe
37 changed files with 319 additions and 570 deletions
|
@ -89,33 +89,33 @@ project.allExtensions.forEach((Project p) -> {
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
List<String> jvmRunArgs = [
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.stage=com.jfoenix",
|
||||
"--add-exports", "javafx.base/com.sun.javafx.binding=com.jfoenix",
|
||||
"--add-exports", "javafx.base/com.sun.javafx.event=com.jfoenix",
|
||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control=com.jfoenix",
|
||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls",
|
||||
"--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.app",
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.core",
|
||||
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
|
||||
"--add-opens", "java.desktop/java.awt=io.xpipe.app",
|
||||
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app",
|
||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app',
|
||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app',
|
||||
"-Xmx8g",
|
||||
"-Dio.xpipe.app.arch=$rootProject.arch",
|
||||
// "-XX:+ExitOnOutOfMemoryError",
|
||||
"-Dfile.encoding=UTF-8",
|
||||
'-XX:+UseZGC',
|
||||
"-Dvisualvm.display.name=XPipe"
|
||||
]
|
||||
project.ext {
|
||||
jvmRunArgs = [
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=com.jfoenix",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.stage=com.jfoenix",
|
||||
"--add-exports", "javafx.base/com.sun.javafx.binding=com.jfoenix",
|
||||
"--add-exports", "javafx.base/com.sun.javafx.event=com.jfoenix",
|
||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control=com.jfoenix",
|
||||
"--add-exports", "javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls",
|
||||
"--add-exports", "javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls",
|
||||
"--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.app",
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
||||
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.core",
|
||||
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
|
||||
"--add-opens", "java.desktop/java.awt=io.xpipe.app",
|
||||
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.app",
|
||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.view=io.xpipe.app',
|
||||
"--add-opens", 'com.dlsc.preferencesfx/com.dlsc.preferencesfx.model=io.xpipe.app',
|
||||
"-Xmx8g",
|
||||
"-Dio.xpipe.app.arch=$rootProject.arch",
|
||||
"-Dfile.encoding=UTF-8",
|
||||
'-XX:+UseZGC',
|
||||
"-Dvisualvm.display.name=XPipe"
|
||||
]
|
||||
}
|
||||
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
|
@ -133,6 +133,7 @@ application {
|
|||
mainModule = 'io.xpipe.app'
|
||||
mainClass = 'io.xpipe.app.Main'
|
||||
applicationDefaultJvmArgs = jvmRunArgs
|
||||
applicationDefaultJvmArgs.add('-XX:+EnableDynamicAgentLoading')
|
||||
}
|
||||
|
||||
run {
|
||||
|
@ -166,6 +167,7 @@ task runAttachedDebugger(type: JavaExec) {
|
|||
"-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.1.jar=port:7857,host:localhost",
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
|
||||
)
|
||||
jvmArgs += '-XX:+EnableDynamicAgentLoading'
|
||||
systemProperties run.systemProperties
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
|||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
|
@ -17,11 +17,15 @@ import java.util.List;
|
|||
public class StoreEntryListComp extends SimpleComp {
|
||||
|
||||
private Comp<?> createList() {
|
||||
var topLevel = StoreViewState.get().getTopLevelSection();
|
||||
var content = new ListBoxViewComp<>(topLevel.getShownChildren(), topLevel.getAllChildren(), (StoreSection e) -> {
|
||||
var custom = StoreSection.customSection(e, true).hgrow();
|
||||
return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10))).styleClass("top");
|
||||
}).apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
|
||||
var content = new ListBoxViewComp<>(
|
||||
StoreViewState.get().getCurrentTopLevelSection().getShownChildren(),
|
||||
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
|
||||
(StoreSection e) -> {
|
||||
var custom = StoreSection.customSection(e, true).hgrow();
|
||||
return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10)))
|
||||
.styleClass("top");
|
||||
})
|
||||
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
|
||||
return content.styleClass("store-list-comp");
|
||||
}
|
||||
|
||||
|
@ -33,18 +37,19 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
return initialCount == StoreViewState.get().getAllEntries().size();
|
||||
},
|
||||
StoreViewState.get().getAllEntries());
|
||||
var map = new LinkedHashMap<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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -20,6 +20,7 @@ public abstract class PlatformMode extends OperationMode {
|
|||
@Override
|
||||
public void onSwitchTo() throws Throwable {
|
||||
if (App.getApp() != null) {
|
||||
StoreViewState.init();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -98,6 +98,10 @@ public interface DataStoreProvider {
|
|||
}
|
||||
|
||||
default DataStoreEntry getDisplayParent(DataStoreEntry store) {
|
||||
return getSyntheticParent(store);
|
||||
}
|
||||
|
||||
default DataStoreEntry getSyntheticParent(DataStoreEntry store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(V v) {
|
||||
return predicate.test(v);
|
||||
}
|
||||
};
|
||||
}, observables));
|
||||
return filteredContentBinding(
|
||||
l2,
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(V v) {
|
||||
return predicate.test(v);
|
||||
}
|
||||
};
|
||||
},
|
||||
Arrays.stream(observables).filter( Objects::nonNull).toArray(Observable[]::new)));
|
||||
}
|
||||
|
||||
public static <V> ObservableList<V> filteredContentBinding(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,29 +219,21 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new));
|
||||
addStoreEntriesIfNotPresent(toAdd.stream()
|
||||
.map(DataStoreEntryRef::get)
|
||||
.toArray(DataStoreEntry[]::new));
|
||||
addStoreEntriesIfNotPresent(toAdd.stream().map(DataStoreEntryRef::get).toArray(DataStoreEntry[]::new));
|
||||
toUpdate.forEach(pair -> {
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
pair.getKey().setStoreInternal(pair.getValue().getStore(), false);
|
||||
},
|
||||
pair.getKey());
|
||||
pair.getKey().setStoreInternal(pair.getValue().getStore(), false);
|
||||
});
|
||||
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).toList());
|
||||
saveAsync();
|
||||
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).collect(Collectors.toSet()));
|
||||
saveAsync();
|
||||
return !newChildren.isEmpty();
|
||||
}
|
||||
|
||||
public void deleteWithChildren(DataStoreEntry... entries) {
|
||||
var toDelete = Arrays.stream(entries)
|
||||
.flatMap(entry -> {
|
||||
// Reverse to delete deepest children first
|
||||
var ordered = getStoreChildren(entry, true);
|
||||
Collections.reverse(ordered);
|
||||
ordered.add(entry);
|
||||
return ordered.stream();
|
||||
var c = getDeepStoreChildren(entry);
|
||||
c.add(entry);
|
||||
return c.stream();
|
||||
})
|
||||
.toList();
|
||||
|
||||
|
@ -185,22 +244,17 @@ public abstract class DataStorage {
|
|||
saveAsync();
|
||||
}
|
||||
|
||||
public void deleteChildren(DataStoreEntry e, boolean deep) {
|
||||
// Reverse to delete deepest children first
|
||||
var ordered = getStoreChildren(e, deep);
|
||||
Collections.reverse(ordered);
|
||||
|
||||
ordered.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntries.removeAll(ordered);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(ordered.toArray(DataStoreEntry[]::new)));
|
||||
public void deleteChildren(DataStoreEntry e) {
|
||||
var c = getDeepStoreChildren(e);
|
||||
c.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntries.removeAll(c);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
|
||||
refreshValidities(false);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public boolean isRootEntry(DataStoreEntry entry) {
|
||||
var noParent = DataStorage.get()
|
||||
.getDisplayParent(entry)
|
||||
.isEmpty();
|
||||
var noParent = DataStorage.get().getDisplayParent(entry).isEmpty();
|
||||
var diffParentCategory = DataStorage.get()
|
||||
.getDisplayParent(entry)
|
||||
.map(p -> !p.getCategoryUuid().equals(entry.getCategoryUuid()))
|
||||
|
@ -229,6 +283,19 @@ public abstract class DataStorage {
|
|||
return current;
|
||||
}
|
||||
|
||||
public Optional<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,26 +393,24 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
public DataStoreEntry getStoreEntry(@NonNull DataStore store) {
|
||||
return getStoreEntryIfPresent(store)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Store not found"));
|
||||
return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found"));
|
||||
}
|
||||
|
||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
|
||||
return storeEntries.stream()
|
||||
.filter(n -> n.getStore() == store || (n.getStore() != null
|
||||
&& Objects.equals(store.getClass(), n.getStore().getClass())
|
||||
&& store.equals(n.getStore())))
|
||||
.filter(n -> n.getStore() == store
|
||||
|| (n.getStore() != null
|
||||
&& Objects.equals(store.getClass(), n.getStore().getClass())
|
||||
&& store.equals(n.getStore())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public abstract boolean supportsSharing();
|
||||
|
||||
public DataStoreCategory getRootCategory(DataStoreCategory category) {
|
||||
DataStoreCategory last = category;
|
||||
DataStoreCategory p = category;
|
||||
while ((p = DataStorage.get()
|
||||
.getStoreCategoryIfPresent(p.getParentCategory())
|
||||
.orElse(null))
|
||||
.getStoreCategoryIfPresent(p.getParentCategory())
|
||||
.orElse(null))
|
||||
!= null) {
|
||||
last = p;
|
||||
}
|
||||
|
@ -402,61 +435,6 @@ public abstract class DataStorage {
|
|||
.findFirst();
|
||||
}
|
||||
|
||||
public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) {
|
||||
var oldParent = DataStorage.get().getDisplayParent(entry);
|
||||
var newParent = DataStorage.get().getDisplayParent(newEntry);
|
||||
var diffParent = Objects.equals(oldParent, newParent);
|
||||
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
newEntry.finalizeEntry();
|
||||
|
||||
var children = getStoreChildren(entry, true);
|
||||
if (!diffParent) {
|
||||
var toRemove = Stream.concat(Stream.of(entry), children.stream())
|
||||
.toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||
}
|
||||
|
||||
entry.applyChanges(newEntry);
|
||||
entry.initializeEntry();
|
||||
|
||||
if (!diffParent) {
|
||||
var toAdd = Stream.concat(Stream.of(entry), children.stream())
|
||||
.toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||
refreshValidities(true);
|
||||
}
|
||||
},
|
||||
entry);
|
||||
}
|
||||
|
||||
public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) {
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
var children = getStoreChildren(entry, true);
|
||||
var toRemove =
|
||||
Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||
|
||||
entry.setCategoryUuid(newCategory.getUuid());
|
||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||
|
||||
var toAdd =
|
||||
Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||
},
|
||||
entry);
|
||||
}
|
||||
|
||||
<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);
|
||||
store.finalizeEntry();
|
||||
this.storeEntries.remove(store);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(store));
|
||||
refreshValidities(false);
|
||||
saveAsync();
|
||||
|
@ -594,19 +581,6 @@ public abstract class DataStorage {
|
|||
this.listeners.forEach(l -> l.onCategoryRemove(cat));
|
||||
}
|
||||
|
||||
public void addListener(StorageListener l) {
|
||||
this.listeners.add(l);
|
||||
}
|
||||
|
||||
public abstract void load();
|
||||
|
||||
public void saveAsync() {
|
||||
// TODO: Don't make this a daemon thread to guarantee proper saving
|
||||
ThreadHelper.unstarted(this::save).start();
|
||||
}
|
||||
|
||||
public abstract void save();
|
||||
|
||||
public Optional<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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
.data-source-config {
|
||||
-fx-padding: 1.5em;
|
||||
-fx-spacing: 1em;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.data-source-type {
|
||||
-fx-padding: 0.15em 0 0.15em 0.05em;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
2
dist/jpackage.gradle
vendored
2
dist/jpackage.gradle
vendored
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue