More service rework

This commit is contained in:
crschnick 2024-06-06 10:00:38 +00:00
parent c1b91f942d
commit 07540e6779
21 changed files with 256 additions and 107 deletions

View file

@ -65,7 +65,9 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
Runnable update = () -> {
// Clear cache of unused values
cache.keySet().removeIf(t -> !all.contains(t));
if (cache.keySet().removeIf(t -> !all.contains(t))) {
int a = 0;
}
var newShown = shown.stream()
.map(v -> {
@ -80,6 +82,10 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
.limit(limit)
.toList();
if (listView.getChildren().equals(newShown)) {
return;
}
for (int i = 0; i < newShown.size(); i++) {
var r = newShown.get(i);
r.pseudoClassStateChanged(ODD, false);
@ -87,10 +93,8 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
r.pseudoClassStateChanged(i % 2 == 0 ? EVEN : ODD, true);
}
if (!listView.getChildren().equals(newShown)) {
var d = new DerivedObservableList<>(listView.getChildren(), true);
d.setContent(newShown);
}
var d = new DerivedObservableList<>(listView.getChildren(), true);
d.setContent(newShown);
};
if (asynchronous) {

View file

@ -1,6 +1,7 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStoreEntry;
@ -52,6 +53,7 @@ public class StoreToggleComp extends SimpleComp {
initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),
v -> {
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(), v);
StoreViewState.get().toggleStoreListUpdate();
});
}

View file

@ -68,7 +68,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
StoreViewState.get().getActiveCategory());
var shownList = all.filtered(
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
return storeEntryWrapper.matchesFilter(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString());

View file

@ -9,13 +9,15 @@ import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.Observable;
import javafx.beans.property.*;
import lombok.Getter;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Getter
public class StoreEntryWrapper {
@ -61,10 +63,6 @@ public class StoreEntryWrapper {
setupListeners();
}
public List<Observable> getUpdateObservables() {
return List.of(category);
}
public void moveTo(DataStoreCategory category) {
ThreadHelper.runAsync(() -> {
DataStorage.get().updateCategory(entry, category);
@ -230,7 +228,7 @@ public class StoreEntryWrapper {
this.expanded.set(!expanded.getValue());
}
public boolean shouldShow(String filter) {
public boolean matchesFilter(String filter) {
if (filter == null || nameProperty().getValue().toLowerCase().contains(filter.toLowerCase())) {
return true;
}

View file

@ -163,8 +163,7 @@ public class StoreSection {
// This check is fast as the children are cached in the storage
var isChildren = DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
var showProvider = !other.getEntry().getValidity().isUsable() ||
other.getEntry().getProvider().shouldShow(other);
var showProvider = !other.getEntry().getValidity().isUsable() || other.getEntry().getProvider().shouldShow(other);
return isChildren && showProvider;
}, e.getPersistentState(), e.getCache(), StoreViewState.get().getEntriesListChangeObservable());
var cached = allChildren.mapped(
@ -182,9 +181,7 @@ public class StoreSection {
// again here
var notRoot =
!DataStorage.get().isRootEntry(section.getWrapper().getEntry());
var showProvider = section.getWrapper().getEntry().getProvider() == null ||
section.getWrapper().getEntry().getProvider().shouldShow(section.getWrapper());
return matchesSelector && showCategory && notRoot && showProvider;
return matchesSelector && showCategory && notRoot;
},
category,
filterString,
@ -213,7 +210,7 @@ public class StoreSection {
}
public boolean matchesFilter(String filter) {
return anyMatches(storeEntryWrapper -> storeEntryWrapper.shouldShow(filter));
return anyMatches(storeEntryWrapper -> storeEntryWrapper.matchesFilter(filter));
}
public boolean anyMatches(Predicate<StoreEntryWrapper> c) {

View file

@ -2,6 +2,7 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
@ -117,6 +118,18 @@ public class StoreViewState {
.orElseThrow()));
}
public void toggleStoreOrderUpdate() {
PlatformThread.runLaterIfNeeded(() -> {
entriesOrderChangeObservable.set(entriesOrderChangeObservable.get() + 1);
});
}
public void toggleStoreListUpdate() {
PlatformThread.runLaterIfNeeded(() -> {
entriesListChangeObservable.set(entriesListChangeObservable.get() + 1);
});
}
private void addListeners() {
if (AppPrefs.get() != null) {
AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> {
@ -136,14 +149,14 @@ public class StoreViewState {
@Override
public void onStoreOrderUpdate() {
Platform.runLater(() -> {
entriesOrderChangeObservable.set(entriesOrderChangeObservable.get() + 1);
toggleStoreOrderUpdate();
});
}
@Override
public void onStoreListUpdate() {
Platform.runLater(() -> {
entriesListChangeObservable.set(entriesListChangeObservable.get() + 1);
toggleStoreListUpdate();
});
}

View file

@ -12,10 +12,8 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
@ -198,10 +196,6 @@ public interface DataStoreProvider {
return getModuleName() + ":" + getId() + "_icon.svg";
}
default Dialog dialogForStore(DataStore store) {
return null;
}
default DataStore defaultStore() {
return null;
}

View file

@ -79,7 +79,7 @@ public class StoreCategoryComp extends SimpleComp {
}));
var shownList = new DerivedObservableList<>(category.getContainedEntries(), true).filtered(
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
return storeEntryWrapper.matchesFilter(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString()).getList();

View file

@ -129,9 +129,17 @@ public class DerivedObservableList<T> {
}
public <V> DerivedObservableList<V> mapped(Function<T, V> map) {
var cache = new HashMap<T, V>();
var l1 = this.<V>createNewDerived();
Runnable runnable = () -> {
l1.setContent(list.stream().map(map).toList());
cache.keySet().removeIf(t -> !getList().contains(t));
l1.setContent(list.stream().map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
}).toList());
};
runnable.run();
list.addListener((ListChangeListener<? super T>) c -> {

View file

@ -58,6 +58,8 @@ public abstract class DataStorage {
@Setter
protected DataStoreCategory selectedCategory;
private final Map<DataStore, DataStoreEntry> storeEntryMapCache = Collections.synchronizedMap(new HashMap<>());
public DataStorage() {
var prefsDir = AppPrefs.get().storageDirectory().getValue();
this.dir = !Files.exists(prefsDir) || !Files.isDirectory(prefsDir) ? AppPrefs.DEFAULT_STORAGE_DIR : prefsDir;
@ -784,12 +786,25 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
return storeEntriesSet.stream()
synchronized (storeEntryMapCache) {
var found = storeEntryMapCache.get(store);
if (found != null) {
return Optional.of(found);
}
}
var found = storeEntriesSet.stream()
.filter(n -> n.getStore() == store || (!identityOnly && (n.getStore() != null
&& Objects.equals(
store.getClass(), n.getStore().getClass())
&& store.equals(n.getStore()))))
.findFirst();
if (found.isPresent()) {
synchronized (storeEntryMapCache) {
storeEntryMapCache.put(store, found.get());
}
}
return found;
}
public DataStoreCategory getRootCategory(DataStoreCategory category) {

View file

@ -0,0 +1,24 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.Validators;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.base.GroupStore;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@SuperBuilder
public abstract class AbstractServiceGroupStore<T extends DataStore> extends JacksonizedValue implements DataStore, GroupStore<T> {
DataStoreEntryRef<T> parent;
@Override
public void checkComplete() throws Throwable {
Validators.nonNull(parent);
}
}

View file

@ -0,0 +1,72 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.base.StoreToggleComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
public abstract class AbstractServiceGroupStoreProvider implements DataStoreProvider {
@Override
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
var t = createToggleComp(sec);
return StoreEntryComp.create(sec.getWrapper(), t, preferLarge);
}
private StoreToggleComp createToggleComp(StoreSection sec) {
var enabled = new SimpleBooleanProperty();
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
var children = DataStorage.get().getStoreChildren(sec.getWrapper().getEntry());
ThreadHelper.runFailableAsync(() -> {
for (DataStoreEntry child : children) {
if (child.getStore() instanceof AbstractServiceStore serviceStore) {
if (aBoolean) {
serviceStore.startSessionIfNeeded();
} else {
serviceStore.stopSessionIfNeeded();
}
}
}
});
});
t.setCustomVisibility(Bindings.createBooleanBinding(() -> {
var children = DataStorage.get().getStoreChildren(sec.getWrapper().getEntry());
for (DataStoreEntry child : children) {
if (child.getStore() instanceof AbstractServiceStore serviceStore) {
if (serviceStore.getHost().getStore().requiresTunnel()) {
return true;
}
}
}
return false;
}, StoreViewState.get().getAllEntries().getList()));
return t;
}
@Override
public Comp<?> stateDisplay(StoreEntryWrapper w) {
return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));
}
@Override
public String getDisplayIconFileName(DataStore store) {
return "base:serviceGroup_icon.svg";
}
@Override
public DataStoreEntry getDisplayParent(DataStoreEntry store) {
AbstractServiceGroupStore<?> s = store.getStore().asNeeded();
return s.getParent().get();
}
}

View file

@ -10,8 +10,8 @@ import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("service")
public class CustomServiceStore extends AbstractServiceStore {
@JsonTypeName("customService")
public final class CustomServiceStore extends AbstractServiceStore {
private final DataStoreEntryRef<NetworkTunnelStore> host;
}

View file

@ -60,7 +60,7 @@ public class CustomServiceStoreProvider extends AbstractServiceStoreProvider {
@Override
public List<String> getPossibleNames() {
return List.of("service");
return List.of("customService");
}
@Override

View file

@ -0,0 +1,11 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import java.util.List;
public interface FixedServiceCreatorStore extends DataStore {
List<? extends DataStoreEntryRef<? extends AbstractServiceStore>> createFixedServices() throws Exception;
}

View file

@ -0,0 +1,39 @@
package io.xpipe.ext.base.service;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FixedHierarchyStore;
import io.xpipe.app.util.Validators;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedChildStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.base.GroupStore;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@SuperBuilder
@Jacksonized
@JsonTypeName("fixedServiceGroup")
public class FixedServiceGroupStore extends JacksonizedValue implements DataStore, GroupStore<FixedServiceCreatorStore>, FixedHierarchyStore {
DataStoreEntryRef<? extends FixedServiceCreatorStore> parent;
@Override
public void checkComplete() throws Throwable {
Validators.nonNull(parent);
}
@Override
@SuppressWarnings("unchecked")
public List<? extends DataStoreEntryRef<? extends FixedChildStore>> listChildren(DataStoreEntry self) throws Exception {
return (List<? extends DataStoreEntryRef<? extends FixedChildStore>>) parent.getStore().createFixedServices();
}
}

View file

@ -0,0 +1,30 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.DataStore;
import java.util.List;
public class FixedServiceGroupStoreProvider extends AbstractServiceGroupStoreProvider {
@Override
public DataStore defaultStore() {
return FixedServiceGroupStore.builder().build();
}
@Override
public DataStoreEntry getDisplayParent(DataStoreEntry store) {
FixedServiceGroupStore s = store.getStore().asNeeded();
return s.getParent().get();
}
@Override
public List<String> getPossibleNames() {
return List.of("fixedServiceGroup");
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(FixedServiceGroupStore.class);
}
}

View file

@ -3,22 +3,30 @@ package io.xpipe.ext.base.service;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedChildStore;
import io.xpipe.core.store.NetworkTunnelStore;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.OptionalInt;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("fixedService")
public class FixedServiceStore extends AbstractServiceStore {
public class FixedServiceStore extends AbstractServiceStore implements FixedChildStore {
private final DataStoreEntryRef<NetworkTunnelStore> host;
private final DataStoreEntryRef<? extends DataStore> parent;
private final DataStoreEntryRef<? extends DataStore> displayParent;
@Override
public DataStoreEntryRef<NetworkTunnelStore> getHost() {
return host;
}
@Override
public OptionalInt getFixedId() {
return OptionalInt.of(getRemotePort());
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
@ -10,9 +11,9 @@ import java.util.List;
public class FixedServiceStoreProvider extends AbstractServiceStoreProvider {
@Override
public DataStoreEntry getDisplayParent(DataStoreEntry store) {
public DataStoreEntry getSyntheticParent(DataStoreEntry store) {
FixedServiceStore s = store.getStore().asNeeded();
return s.getParent().get();
return DataStorage.get().getOrCreateNewSyntheticEntry(s.getHost().get(), "Services", FixedServiceGroupStore.builder().parent(s.getDisplayParent().get().ref()).build());
}
@Override

View file

@ -1,70 +1,11 @@
package io.xpipe.ext.base.service;
import io.xpipe.app.comp.base.StoreToggleComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class ServiceGroupStoreProvider implements DataStoreProvider {
@Override
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
var t = createToggleComp(sec);
return StoreEntryComp.create(sec.getWrapper(), t, preferLarge);
}
private StoreToggleComp createToggleComp(StoreSection sec) {
var enabled = new SimpleBooleanProperty();
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
var children = DataStorage.get().getStoreChildren(sec.getWrapper().getEntry());
ThreadHelper.runFailableAsync(() -> {
for (DataStoreEntry child : children) {
if (child.getStore() instanceof AbstractServiceStore serviceStore) {
if (aBoolean) {
serviceStore.startSessionIfNeeded();
} else {
serviceStore.stopSessionIfNeeded();
}
}
}
});
});
t.setCustomVisibility(Bindings.createBooleanBinding(() -> {
var children = DataStorage.get().getStoreChildren(sec.getWrapper().getEntry());
for (DataStoreEntry child : children) {
if (child.getStore() instanceof AbstractServiceStore serviceStore) {
if (serviceStore.getHost().getStore().requiresTunnel()) {
return true;
}
}
}
return false;
}, StoreViewState.get().getAllEntries().getList()));
return t;
}
@Override
public Comp<?> stateDisplay(StoreEntryWrapper w) {
return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));
}
@Override
public String getDisplayIconFileName(DataStore store) {
return "base:serviceGroup_icon.svg";
}
public class ServiceGroupStoreProvider extends AbstractServiceGroupStoreProvider {
@Override
public DataStore defaultStore() {

View file

@ -10,10 +10,7 @@ import io.xpipe.ext.base.desktop.DesktopEnvironmentStoreProvider;
import io.xpipe.ext.base.script.ScriptDataStorageProvider;
import io.xpipe.ext.base.script.ScriptGroupStoreProvider;
import io.xpipe.ext.base.script.SimpleScriptStoreProvider;
import io.xpipe.ext.base.service.FixedServiceStoreProvider;
import io.xpipe.ext.base.service.ServiceGroupStoreProvider;
import io.xpipe.ext.base.service.ServiceOpenAction;
import io.xpipe.ext.base.service.CustomServiceStoreProvider;
import io.xpipe.ext.base.service.*;
import io.xpipe.ext.base.store.StorePauseAction;
import io.xpipe.ext.base.store.StoreStartAction;
import io.xpipe.ext.base.store.StoreStopAction;
@ -75,11 +72,6 @@ open module io.xpipe.ext.base {
EditStoreAction,
DeleteStoreChildrenAction,
BrowseStoreAction;
provides DataStoreProvider with ServiceGroupStoreProvider, CustomServiceStoreProvider, FixedServiceStoreProvider,
SimpleScriptStoreProvider,
DesktopEnvironmentStoreProvider,
DesktopApplicationStoreProvider,
DesktopCommandStoreProvider,
ScriptGroupStoreProvider;
provides DataStoreProvider with FixedServiceGroupStoreProvider, ServiceGroupStoreProvider, CustomServiceStoreProvider, FixedServiceStoreProvider, SimpleScriptStoreProvider, DesktopEnvironmentStoreProvider, DesktopApplicationStoreProvider, DesktopCommandStoreProvider, ScriptGroupStoreProvider;
provides DataStorageExtensionProvider with ScriptDataStorageProvider;
}