State rework

This commit is contained in:
crschnick 2024-04-16 13:47:29 +00:00
parent dd3ccf37d3
commit 5e80c1a4a1
17 changed files with 155 additions and 274 deletions

View file

@ -8,8 +8,10 @@ import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import lombok.Setter;
import java.util.function.Consumer;
@ -20,6 +22,8 @@ public class StoreToggleComp extends SimpleComp {
private final StoreSection section;
private final BooleanProperty value;
private final Consumer<Boolean> onChange;
@Setter
private ObservableBooleanValue customVisibility = new SimpleBooleanProperty(true);
public StoreToggleComp(String nameKey, StoreSection section, boolean initial, Consumer<Boolean> onChange) {
this.nameKey = nameKey;
@ -28,16 +32,28 @@ public class StoreToggleComp extends SimpleComp {
this.onChange = onChange;
}
public StoreToggleComp(String nameKey, StoreSection section, BooleanProperty initial, Consumer<Boolean> onChange) {
this.nameKey = nameKey;
this.section = section;
this.value = initial;
this.onChange = onChange;
}
@Override
protected Region createSimple() {
var disable = section.getWrapper().getValidity().map(state -> state != DataStoreEntry.Validity.COMPLETE);
var visible = Bindings.createBooleanBinding(
() -> {
if (!this.customVisibility.get()) {
return false;
}
return section.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.COMPLETE
&& section.getShowDetails().get();
},
section.getWrapper().getValidity(),
section.getShowDetails());
section.getShowDetails(),
this.customVisibility);
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
.visible(visible)
.disable(disable);

View file

@ -114,7 +114,7 @@ public abstract class StoreEntryComp extends SimpleComp {
var loading = LoadingOverlayComp.noProgress(
Comp.of(() -> button),
wrapper.getInRefresh().and(wrapper.getObserving().not()));
wrapper.getBusy().or(wrapper.getEntry().getProvider().busy(wrapper)));
return loading.createRegion();
}

View file

@ -10,12 +10,12 @@ import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import lombok.Getter;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -26,15 +26,14 @@ public class StoreEntryWrapper {
private final DataStoreEntry entry;
private final Property<Instant> lastAccess;
private final BooleanProperty disabled = new SimpleBooleanProperty();
private final BooleanProperty inRefresh = new SimpleBooleanProperty();
private final BooleanProperty observing = new SimpleBooleanProperty();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final Property<DataStoreEntry.Validity> validity = new SimpleObjectProperty<>();
private final Map<ActionProvider, BooleanProperty> actionProviders;
private final Property<ActionProvider.DefaultDataStoreCallSite<?>> defaultActionProvider;
private final BooleanProperty deletable = new SimpleBooleanProperty();
private final BooleanProperty expanded = new SimpleBooleanProperty();
private final Property<Object> persistentState = new SimpleObjectProperty<>();
private final MapProperty<String, Object> cache = new SimpleMapProperty<>(FXCollections.observableHashMap());
private final Property<Map<String, Object>> cache = new SimpleObjectProperty<>(Map.of());
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
private final Property<String> summary = new SimpleObjectProperty<>();
@ -112,12 +111,12 @@ public class StoreEntryWrapper {
disabled.setValue(entry.isDisabled());
validity.setValue(entry.getValidity());
expanded.setValue(entry.isExpanded());
observing.setValue(entry.isObserving());
persistentState.setValue(entry.getStorePersistentState());
cache.putAll(entry.getStoreCache());
// Use map copy to recognize update
cache.setValue(new HashMap<>(entry.getStoreCache()));
color.setValue(entry.getColor());
inRefresh.setValue(entry.isInRefresh());
busy.setValue(entry.isInRefresh());
deletable.setValue(entry.getConfiguration().isDeletable()
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());

View file

@ -18,12 +18,19 @@ import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import java.util.List;
public interface DataStoreProvider {
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
return new SimpleBooleanProperty(false);
}
default boolean editByDefault() {
return false;
}

View file

@ -0,0 +1,68 @@
package io.xpipe.app.ext;
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.fxcomps.Comp;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.SingletonSessionStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
public interface SingletonSessionStoreProvider extends DataStoreProvider {
@Override
public default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
return Bindings.createBooleanBinding(() -> {
SingletonSessionStore<?> s = wrapper.getEntry().getStore().asNeeded();
return s.isEnabled() != s.isRunning();
}, wrapper.getCache());
}
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
var t = createToggleComp(sec);
return StoreEntryComp.create(sec.getWrapper(), t, preferLarge);
}
default Comp<?> createToggleComp(StoreSection sec) {
var enabled = new SimpleBooleanProperty();
sec.getWrapper().getCache().subscribe((newValue) -> {
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
enabled.set(s.isEnabled());
});
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
if (s.isEnabled() != aBoolean) {
ThreadHelper.runFailableAsync(() -> {
if (aBoolean) {
s.startSessionIfNeeded();
} else {
s.stopSessionIfNeeded();
}
});
}
});
return t;
}
public default Comp<?> stateDisplay(StoreEntryWrapper w) {
return new SystemStateComp(
Bindings.createObjectBinding(
() -> {
SingletonSessionStore<?> s = w.getEntry().getStore().asNeeded();
if (!s.isEnabled()) {
return SystemStateComp.State.OTHER;
}
return s.isRunning()
? SystemStateComp.State.SUCCESS
: SystemStateComp.State.FAILURE;
},
w.getCache()));
}
}

View file

@ -1,71 +0,0 @@
package io.xpipe.app.ext;
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.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.SingletonToggleSessionStore;
import io.xpipe.core.store.ToggleSessionState;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
public interface SingletonToggleSessionStoreProvider extends DataStoreProvider {
@Override
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
SingletonToggleSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
var enabled = new SimpleBooleanProperty();
sec.getWrapper().getPersistentState().subscribe((newValue) -> {
var rdps = (ToggleSessionState) newValue;
enabled.set(rdps.getEnabled() != null ? rdps.getEnabled() : false);
});
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
var state = s.getState();
if (state.getEnabled() != aBoolean) {
state.setEnabled(aBoolean);
s.setState(state);
sec.getWrapper().getEntry().validate();
}
});
return StoreEntryComp.create(sec.getWrapper(), t, preferLarge);
}
public default Comp<?> stateDisplay(StoreEntryWrapper w) {
SingletonToggleSessionStore<?> st = w.getEntry().getStore().asNeeded();
return new SystemStateComp(
Bindings.createObjectBinding(
() -> {
ToggleSessionState s = (ToggleSessionState) w.getPersistentState().getValue();
if (s.getEnabled() == null || !s.getEnabled()) {
return SystemStateComp.State.OTHER;
}
return s.getRunning() != null && s.getRunning()
? SystemStateComp.State.SUCCESS
: SystemStateComp.State.FAILURE;
},
w.getPersistentState(),
w.getCache()));
}
@Override
public default void storageInit() {
for (DataStoreEntry e : DataStorage.get().getStoreEntries()) {
if (getStoreClasses().stream()
.anyMatch(aClass ->
e.getStore() != null && e.getStore().getClass().equals(aClass))) {
SingletonToggleSessionStore<?> tunnelStore = e.getStore().asNeeded();
var state = tunnelStore.getState();
state.setEnabled(false);
tunnelStore.setState(state);
}
}
}
}

View file

@ -47,10 +47,6 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
boolean inRefresh;
@NonFinal
@Setter
boolean observing;
@Getter
@NonFinal
DataStoreProvider provider;

View file

@ -1,48 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.InternalCacheDataStore;
import java.util.Timer;
import java.util.TimerTask;
public interface ObservableDataStore extends DataStore, InternalCacheDataStore {
default void toggleObserverState(boolean state) {
setObserverState(state);
var entry = DataStorage.get().getStoreEntry(this);
entry.setObserving(state);
if (state) {
var timer = getCache("oberserverTimer", Timer.class, null);
if (timer == null) {
timer = new Timer(true);
timer.scheduleAtFixedRate(
new TimerTask() {
@Override
public void run() {
if (getObserverState()) {
refresh();
}
}
},
0,
20000);
setCache("oberserverTimer", timer);
}
}
}
default boolean getObserverState() {
return getCache("observerState", Boolean.class, false);
}
default void setObserverState(boolean state) {
setCache("observerState", state);
}
private void refresh() {
var entry = DataStorage.get().getStoreEntry(this);
DataStorage.get().refreshChildren(entry);
}
}

View file

@ -1,6 +1,6 @@
package io.xpipe.core.store;
public interface SingletonSessionStore<T extends SingletonSessionStore.Session> extends InternalCacheDataStore {
public interface SingletonSessionStore<T extends SingletonSessionStore.Session> extends ExpandedLifecycleStore, InternalCacheDataStore {
static abstract class Session {
@ -11,7 +11,27 @@ public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
public abstract void stop() throws Exception;
}
void onSessionUpdate(boolean active);
@Override
public default void finalizeValidate() throws Exception {
stopSessionIfNeeded();
}
default void setEnabled(boolean value) {
setCache("sessionEnabled", value);
}
default boolean isRunning() {
return getCache("sessionRunning", Boolean.class, false);
}
default boolean isEnabled() {
return getCache("sessionEnabled",Boolean.class,false);
}
default void onSessionUpdate(boolean active) {
setEnabled(active);
setCache("sessionRunning", active);
}
T newSession() throws Exception;
@ -22,30 +42,36 @@ public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
return (T) getCache("session", getSessionClass(), null);
}
default void startIfNeeded() throws Exception {
var s = getSession();
if (s != null) {
if (s.isRunning()) {
default void startSessionIfNeeded() throws Exception {
synchronized (this) {
var s = getSession();
setEnabled(true);
if (s != null) {
if (s.isRunning()) {
return;
}
s.start();
return;
}
s = newSession();
s.start();
return;
setCache("session", s);
onSessionUpdate(true);
}
s = newSession();
s.start();
setCache("session", s);
onSessionUpdate(true);
}
default void stopIfNeeded() throws Exception {
var ex = getSession();
setCache("session", null);
if (ex != null) {
ex.stop();
onSessionUpdate(false);
default void stopSessionIfNeeded() throws Exception {
synchronized (this) {
var ex = getSession();
setEnabled(false);
if (ex != null) {
ex.stop();
setCache("session", null);
onSessionUpdate(false);
}
}
}
}

View file

@ -1,33 +0,0 @@
package io.xpipe.core.store;
public interface SingletonToggleSessionStore<T extends SingletonSessionStore.Session> extends SingletonSessionStore<T>, StatefulDataStore<ToggleSessionState>, ValidatableStore {
@Override
default Class<ToggleSessionState> getStateClass() {
return ToggleSessionState.class;
}
@Override
public default void onSessionUpdate(boolean active) {
var c = getState();
c.setRunning(active);
if (!active) {
c.setEnabled(false);
} else {
c.setEnabled(true);
}
setState(c);
}
@Override
public default void validate() throws Exception {
if (getState().getEnabled() != null) {
if (getState().getEnabled()) {
startIfNeeded();
} else {
stopIfNeeded();
}
}
}
}

View file

@ -1,23 +0,0 @@
package io.xpipe.core.store;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@Getter
@Setter
@SuperBuilder
@Jacksonized
public class ToggleSessionState extends DataStoreState {
Boolean enabled;
Boolean running;
@Override
public void merge(DataStoreState newer) {
var state = (ToggleSessionState) newer;
enabled = useNewer(enabled, state.enabled);
running = useNewer(running, state.running);
}
}

View file

@ -1,55 +0,0 @@
package io.xpipe.ext.base.action;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.ObservableDataStore;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class ObserveStoreAction implements ActionProvider {
@Override
public DataStoreCallSite<?> getDataStoreCallSite() {
return new DataStoreCallSite<ObservableDataStore>() {
@Override
public ActionProvider.Action createAction(DataStoreEntryRef<ObservableDataStore> store) {
return new Action(store);
}
@Override
public Class<ObservableDataStore> getApplicableClass() {
return ObservableDataStore.class;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ObservableDataStore> store) {
return store.getStore().getObserverState()
? AppI18n.observable("base.stopObserve")
: AppI18n.observable("base.observe");
}
@Override
public String getIcon(DataStoreEntryRef<ObservableDataStore> store) {
return store.getStore().getObserverState() ? "mdi2e-eye-off-outline" : "mdi2e-eye-outline";
}
};
}
@Value
static class Action implements ActionProvider.Action {
DataStoreEntryRef<ObservableDataStore> store;
@Override
public boolean requiresJavaFXPlatform() {
return true;
}
@Override
public void execute() {
store.getStore().toggleObserverState(!store.getStore().getObserverState());
}
}
}

View file

@ -9,7 +9,7 @@ import io.xpipe.app.util.FixedHierarchyStore;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class RefreshStoreAction implements ActionProvider {
public class RefreshStoreChildrenAction implements ActionProvider {
@Override
public ActionProvider.DataStoreCallSite<?> getDataStoreCallSite() {

View file

@ -32,7 +32,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public void execute() throws Exception {
s.getDesktop().getStore().runScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
s.getDesktop().getStore().runDesktopScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
}
};
}
@ -48,7 +48,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public void execute() throws Exception {
s.getDesktop().getStore().runScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
s.getDesktop().getStore().runDesktopScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
}
};
}

View file

@ -9,9 +9,9 @@ public interface DesktopBaseStore extends DataStore {
boolean supportsDesktopAccess();
void runScript(String name, ShellDialect dialect, String script) throws Exception;
void runDesktopScript(String name, ShellDialect dialect, String script) throws Exception;
void runTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception;
void runDesktopTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception;
ShellDialect getUsedDialect();

View file

@ -53,12 +53,12 @@ public class DesktopEnvironmentStore extends JacksonizedValue implements Desktop
public void launch(String n, String commands) throws Exception {
var fullName = n + " [" + getSelfEntry().getName() + "]";
base.getStore().runScript(fullName, dialect, getMergedInitCommands(commands));
base.getStore().runDesktopScript(fullName, dialect, getMergedInitCommands(commands));
}
public void launchSelf() throws Exception {
var fullName = getSelfEntry().getName();
base.getStore().runTerminal(fullName, terminal, dialect, getMergedInitCommands(null));
base.getStore().runDesktopTerminal(fullName, terminal, dialect, getMergedInitCommands(null));
}
@Override
@ -67,13 +67,13 @@ public class DesktopEnvironmentStore extends JacksonizedValue implements Desktop
}
@Override
public void runScript(String name, ShellDialect dialect, String script) throws Exception {
base.getStore().runScript(name, dialect, script);
public void runDesktopScript(String name, ShellDialect dialect, String script) throws Exception {
base.getStore().runDesktopScript(name, dialect, script);
}
@Override
public void runTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception {
base.getStore().runTerminal(name,terminalType,dialect,script);
public void runDesktopTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception {
base.getStore().runDesktopTerminal(name,terminalType,dialect,script);
}
@Override

View file

@ -58,8 +58,7 @@ open module io.xpipe.ext.base {
StoreStopAction,
StoreStartAction,
StorePauseAction,
CloneStoreAction,
RefreshStoreAction,
CloneStoreAction, RefreshStoreChildrenAction,
ScanAction,
LaunchAction,
XPipeUrlAction,