From 0ed7b2fcc95eec0680fb65edb67beb042aa1a9d2 Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 19 Sep 2024 13:02:37 +0000 Subject: [PATCH] Rework validation and state management --- .../impl/ConnectionAddExchangeImpl.java | 2 +- .../impl/ConnectionRefreshExchangeImpl.java | 2 +- .../app/comp/store/StoreCreationComp.java | 35 ++++++----- .../app/comp/store/StoreCreationMenu.java | 2 +- .../comp/store/StoreIconChoiceDialogComp.java | 2 +- .../xpipe/app/comp/store/StoreIntroComp.java | 2 +- .../java/io/xpipe/app/core/mode/BaseMode.java | 1 - .../io/xpipe/app/core/mode/OperationMode.java | 26 +++++--- .../app/fxcomps/impl/DataStoreChoiceComp.java | 18 ++++-- .../app/resources/FileAutoSystemIcon.java | 35 +++++++++++ .../io/xpipe/app/resources/SystemIcons.java | 4 +- .../io/xpipe/app/storage/DataStoreEntry.java | 61 ++++++++++++++++--- .../java/io/xpipe/app/util/ScanAlert.java | 41 +++++++------ .../xpipe/app/resources/misc/vault_empty.md | 2 +- .../java/io/xpipe/core/store/ShellStore.java | 12 ++-- .../core/store/ShellValidationContext.java | 22 +++++++ .../io/xpipe/core/store/ValidatableStore.java | 8 ++- .../xpipe/core/store/ValidationContext.java | 8 +++ .../ext/base/action/ScanStoreAction.java | 2 +- .../ext/base/store/ShellStoreProvider.java | 15 ++++- 20 files changed, 225 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/resources/FileAutoSystemIcon.java create mode 100644 core/src/main/java/io/xpipe/core/store/ShellValidationContext.java create mode 100644 core/src/main/java/io/xpipe/core/store/ValidationContext.java diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java index 7cf0c747f..325f0db3e 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java @@ -21,7 +21,7 @@ public class ConnectionAddExchangeImpl extends ConnectionAddExchange { try { DataStorage.get().addStoreEntryInProgress(entry); if (msg.getValidate()) { - entry.validateOrThrow(); + entry.validateOrThrow(true); } } catch (Throwable ex) { if (ex instanceof ValidationException) { diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java index 5fa336528..6df2edf0a 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java @@ -17,7 +17,7 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange { if (e.getStore() instanceof FixedHierarchyStore) { DataStorage.get().refreshChildren(e, true); } else { - e.validateOrThrow(); + e.validateOrThrow(true); } return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java index c2c97fbbf..4134f750a 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java @@ -1,5 +1,6 @@ package io.xpipe.app.comp.store; +import atlantafx.base.controls.Spacer; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.DialogComp; import io.xpipe.app.comp.base.ErrorOverlayComp; @@ -20,8 +21,8 @@ import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.*; import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.ValidationContext; import io.xpipe.core.util.ValidationException; - import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -33,8 +34,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.stage.Stage; - -import atlantafx.base.controls.Spacer; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; import net.synedra.validatorfx.GraphicDecorationStackPane; @@ -42,14 +41,13 @@ import net.synedra.validatorfx.GraphicDecorationStackPane; import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.function.BiConsumer; import java.util.function.Predicate; @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class StoreCreationComp extends DialogComp { Stage window; - BiConsumer consumer; + CreationConsumer consumer; Property provider; ObjectProperty store; Predicate filter; @@ -67,7 +65,7 @@ public class StoreCreationComp extends DialogComp { public StoreCreationComp( Stage window, - BiConsumer consumer, + CreationConsumer consumer, Property provider, ObjectProperty store, Predicate filter, @@ -165,7 +163,7 @@ public class StoreCreationComp extends DialogComp { e.getProvider(), e.getStore(), v -> true, - (newE, validated) -> { + (newE, context, validated) -> { ThreadHelper.runAsync(() -> { if (!DataStorage.get().getStoreEntries().contains(e)) { DataStorage.get().addStoreEntryIfNotPresent(newE); @@ -193,7 +191,7 @@ public class StoreCreationComp extends DialogComp { base != null ? DataStoreProviders.byStore(base) : null, base, dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()), - (e, validated) -> { + (e, context, validated) -> { try { DataStorage.get().addStoreEntryIfNotPresent(e); if (validated @@ -201,7 +199,7 @@ public class StoreCreationComp extends DialogComp { && AppPrefs.get() .openConnectionSearchWindowOnConnectionCreation() .get()) { - ScanAlert.showAsync(e); + ScanAlert.showAsync(e, context); } } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); @@ -211,12 +209,17 @@ public class StoreCreationComp extends DialogComp { null); } + private static interface CreationConsumer { + + void consume(DataStoreEntry entry, ValidationContext validationContext, boolean validated); + } + private static void show( String initialName, DataStoreProvider provider, DataStore s, Predicate filter, - BiConsumer con, + CreationConsumer con, boolean staticDisplay, DataStoreEntry existingEntry) { var prop = new SimpleObjectProperty<>(provider); @@ -247,7 +250,7 @@ public class StoreCreationComp extends DialogComp { return List.of( new ButtonComp(AppI18n.observable("skip"), null, () -> { if (showInvalidConfirmAlert()) { - commit(false); + commit(null, false); } else { finish(); } @@ -287,7 +290,7 @@ public class StoreCreationComp extends DialogComp { // We didn't change anything if (existingEntry != null && existingEntry.getStore().equals(store.getValue())) { - commit(false); + commit(null, false); return; } @@ -317,8 +320,8 @@ public class StoreCreationComp extends DialogComp { try (var b = new BooleanScope(busy).start()) { DataStorage.get().addStoreEntryInProgress(entry.getValue()); - entry.getValue().validateOrThrow(); - commit(true); + var context = entry.getValue().validateOrThrow(false); + commit(context, true); } catch (Throwable ex) { if (ex instanceof ValidationException) { ErrorEvent.expected(ex); @@ -403,14 +406,14 @@ public class StoreCreationComp extends DialogComp { .createRegion(); } - private void commit(boolean validated) { + private void commit(ValidationContext validationContext, boolean validated) { if (finished.get()) { return; } finished.setValue(true); if (entry.getValue() != null) { - consumer.accept(entry.getValue(), validated); + consumer.consume(entry.getValue(), validationContext, validated); } PlatformThread.runLaterIfNeeded(() -> { diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java index 13ad741f3..819e09118 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java @@ -22,7 +22,7 @@ public class StoreCreationMenu { automatically.setGraphic(new FontIcon("mdi2e-eye-plus-outline")); automatically.textProperty().bind(AppI18n.observable("addAutomatically")); automatically.setOnAction(event -> { - ScanAlert.showAsync(null); + ScanAlert.showAsync(null, null); event.consume(); }); menu.getItems().add(automatically); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java index b571397c1..27a21edf3 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java @@ -55,7 +55,7 @@ public class StoreIconChoiceDialogComp extends SimpleComp { var dialog = new DialogComp() { @Override protected void finish() { - entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null); + entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null, true); dialogStage.close(); } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java index 6ae9626f3..c427fca82 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java @@ -39,7 +39,7 @@ public class StoreIntroComp extends SimpleComp { var scanButton = new Button(null, new FontIcon("mdi2m-magnify")); scanButton.textProperty().bind(AppI18n.observable("detectConnections")); - scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local())); + scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local(), null)); scanButton.setDefaultButton(true); var scanPane = new StackPane(scanButton); scanPane.setAlignment(Pos.CENTER); diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index bf33e7ca5..46475ae44 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -87,7 +87,6 @@ public class BaseMode extends OperationMode { AppDataLock.unlock(); BlobManager.reset(); FileBridge.reset(); - // Shut down server last to keep a non-daemon thread running AppBeaconServer.reset(); TrackEvent.info("Background mode shutdown finished"); } diff --git a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java index fd5b6c206..38893c8d4 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java @@ -293,17 +293,27 @@ public abstract class OperationMode { inShutdown = true; OperationMode.inShutdownHook = inShutdownHook; - try { - if (CURRENT != null) { - CURRENT.finalTeardown(); + // Keep a non-daemon thread running + var thread = ThreadHelper.createPlatformThread("shutdown", false, () -> { + try { + if (CURRENT != null) { + CURRENT.finalTeardown(); + } + CURRENT = null; + } catch (Throwable t) { + ErrorEvent.fromThrowable(t).term().handle(); + OperationMode.halt(1); } - CURRENT = null; - } catch (Throwable t) { - ErrorEvent.fromThrowable(t).term().handle(); + + OperationMode.halt(hasError ? 1 : 0); + }); + thread.start(); + + try { + thread.join(); + } catch (InterruptedException ignored) { OperationMode.halt(1); } - - OperationMode.halt(hasError ? 1 : 0); } // public static synchronized void reload() { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java index 24e8ecc9c..2476f784e 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java @@ -6,6 +6,7 @@ import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.resources.SystemIcons; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntryRef; @@ -200,18 +201,23 @@ public class DataStoreChoiceComp extends SimpleComp { button.apply(struc -> { struc.get().setMaxWidth(2000); struc.get().setAlignment(Pos.CENTER_LEFT); - Comp graphic = new PrettySvgComp( + Comp graphic = PrettyImageHelper.ofFixedSize( Bindings.createStringBinding( () -> { if (selected.getValue() == null) { return null; } - return selected.getValue() - .get() - .getProvider() - .getDisplayIconFileName( - selected.getValue().getStore()); + if (selected.getValue().get().getIcon() == null) { + return selected.getValue() + .get() + .getProvider() + .getDisplayIconFileName( + selected.getValue().getStore()); + } + + SystemIcons.load(); + return "app:system/" + selected.getValue().get().getIcon() + ".svg"; }, selected), 16, diff --git a/app/src/main/java/io/xpipe/app/resources/FileAutoSystemIcon.java b/app/src/main/java/io/xpipe/app/resources/FileAutoSystemIcon.java new file mode 100644 index 000000000..186d98b67 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/resources/FileAutoSystemIcon.java @@ -0,0 +1,35 @@ +package io.xpipe.app.resources; + +import io.xpipe.core.process.OsType; +import io.xpipe.core.process.ShellControl; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=true) +public class FileAutoSystemIcon extends SystemIcon { + + OsType.Any osType; + String file; + + public FileAutoSystemIcon(String iconName, String displayName, OsType.Any osType, String file) { + super(iconName, displayName); + this.osType = osType; + this.file = file; + } + + @Override + public boolean isApplicable(ShellControl sc) throws Exception { + if (sc.getOsType() != osType) { + return false; + } + + var abs = sc.getShellDialect().evaluateExpression(sc, file).readStdoutIfPossible(); + if (abs.isEmpty()) { + return false; + } + + return sc.getShellDialect().createFileExistsCommand(sc, abs.get()).executeAndCheck() || + sc.getShellDialect().directoryExists(sc, abs.get()).executeAndCheck(); + } +} diff --git a/app/src/main/java/io/xpipe/app/resources/SystemIcons.java b/app/src/main/java/io/xpipe/app/resources/SystemIcons.java index d8c2a8497..421007f9f 100644 --- a/app/src/main/java/io/xpipe/app/resources/SystemIcons.java +++ b/app/src/main/java/io/xpipe/app/resources/SystemIcons.java @@ -1,5 +1,6 @@ package io.xpipe.app.resources; +import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellStoreState; @@ -33,7 +34,8 @@ public class SystemIcons { shellStoreState.getShellDialect() == ShellDialects.PFSENSE; } }, - new ContainerAutoSystemIcon("file-browser", "File Browser", name -> name.contains("filebrowser")) + new ContainerAutoSystemIcon("file-browser", "File Browser", name -> name.contains("filebrowser")), + new FileAutoSystemIcon("syncthing", "Syncthing", OsType.LINUX, "~/.local/state/syncthing") ); private static final List SYSTEM_ICONS = new ArrayList<>(); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 19e9d8ecd..a08df80a7 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -188,7 +188,7 @@ public class DataStoreEntry extends StorageElement { var icon = SystemIcons.detectForStore(store); if (icon.isPresent()) { - setIcon(icon.get().getIconName()); + setIcon(icon.get().getIconName(), true); } } @@ -370,7 +370,11 @@ public class DataStoreEntry extends StorageElement { return (T) storePersistentState; } - public void setIcon(String icon) { + public void setIcon(String icon, boolean force) { + if (this.icon != null && !force) { + return; + } + var changed = !Objects.equals(this.icon, icon); this.icon = icon; if (changed) { @@ -493,15 +497,24 @@ public class DataStoreEntry extends StorageElement { dirty = true; } - public void validate() { + public > void validate() { try { - validateOrThrow(); + validateOrThrow(true); } catch (Throwable ex) { ErrorEvent.fromThrowable(ex).handle(); } } - public void validateOrThrow() throws Throwable { + public > void validate(T context) { + try { + validateOrThrow(context); + } catch (Throwable ex) { + ErrorEvent.fromThrowable(ex).handle(); + } + } + + @SuppressWarnings("unchecked") + public > void validateOrThrow(T context) throws Throwable { if (store == null) { return; } @@ -509,8 +522,8 @@ public class DataStoreEntry extends StorageElement { try { store.checkComplete(); incrementBusyCounter(); - if (store instanceof ValidatableStore l) { - l.validate(); + if (store instanceof ValidatableStore l) { + ((ValidatableStore) l).validate(context); } else if (store instanceof FixedHierarchyStore h) { childrenCache = h.listChildren(this).stream() .map(DataStoreEntryRef::get) @@ -521,6 +534,40 @@ public class DataStoreEntry extends StorageElement { } } + @SuppressWarnings("unchecked") + public ValidationContext validateOrThrow(boolean close) throws Throwable { + if (store == null) { + return null; + } + + try { + store.checkComplete(); + incrementBusyCounter(); + if (store instanceof ValidatableStore l) { + ValidationContext context = (ValidationContext) l.createContext(); + try { + ((ValidatableStore>) l).validate(context); + } catch (Throwable t) { + context.close(); + throw t; + } + if (close) { + context.close(); + } + return context; + } else if (store instanceof FixedHierarchyStore h) { + childrenCache = h.listChildren(this).stream() + .map(DataStoreEntryRef::get) + .collect(Collectors.toSet()); + return null; + } else { + return null; + } + } finally { + decrementBusyCounter(); + } + } + public void refreshStore() { if (validity == Validity.LOAD_FAILED) { return; diff --git a/app/src/main/java/io/xpipe/app/util/ScanAlert.java b/app/src/main/java/io/xpipe/app/util/ScanAlert.java index 1dfdac33c..e3b9b0a4a 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanAlert.java +++ b/app/src/main/java/io/xpipe/app/util/ScanAlert.java @@ -16,6 +16,8 @@ import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.store.ShellStore; +import io.xpipe.core.store.ShellValidationContext; +import io.xpipe.core.store.ValidationContext; import javafx.application.Platform; import javafx.beans.property.*; import javafx.beans.value.ObservableValue; @@ -34,7 +36,7 @@ import static javafx.scene.layout.Priority.ALWAYS; public class ScanAlert { - public static void showAsync(DataStoreEntry entry) { + public static void showAsync(DataStoreEntry entry, ValidationContext context) { ThreadHelper.runAsync(() -> { var showForCon = entry == null || (entry.getStore() instanceof ShellStore @@ -42,12 +44,12 @@ public class ScanAlert { || shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE)); if (showForCon) { - showForShellStore(entry); + showForShellStore(entry, (ShellValidationContext) context); } }); } - public static void showForShellStore(DataStoreEntry initial) { + public static void showForShellStore(DataStoreEntry initial, ShellValidationContext context) { show(initial, (DataStoreEntry entry, ShellControl sc) -> { if (!sc.canHaveSubshells()) { return null; @@ -76,15 +78,16 @@ public class ScanAlert { } } return applicable; - }); + }, context); } private static void show( DataStoreEntry initialStore, - BiFunction> applicable) { + BiFunction> applicable, + ShellValidationContext shellValidationContext) { DialogComp.showWindow( "scanAlertTitle", - stage -> new Dialog(stage, initialStore != null ? initialStore.ref() : null, applicable)); + stage -> new Dialog(stage, initialStore != null ? initialStore.ref() : null, applicable, shellValidationContext)); } private static class Dialog extends DialogComp { @@ -96,16 +99,18 @@ public class ScanAlert { private final ListProperty selected = new SimpleListProperty<>(FXCollections.observableArrayList()); private final BooleanProperty busy = new SimpleBooleanProperty(); - private ShellControl shellControl; + private ShellValidationContext shellValidationContext; private Dialog( Stage window, DataStoreEntryRef entry, - BiFunction> applicable) { + BiFunction> applicable, ShellValidationContext shellValidationContext + ) { this.window = window; this.initialStore = entry; this.entry = new SimpleObjectProperty<>(entry); this.applicable = applicable; + this.shellValidationContext = shellValidationContext; } @Override @@ -138,7 +143,7 @@ public class ScanAlert { } // Previous scan operation could have exited the shell - shellControl.start(); + shellValidationContext.get().start(); try { a.getScanner().run(); @@ -148,10 +153,8 @@ public class ScanAlert { } }); } finally { - if (shellControl != null) { - shellControl.close(); - } - shellControl = null; + shellValidationContext.close(); + shellValidationContext = null; } }); } @@ -198,15 +201,13 @@ public class ScanAlert { ThreadHelper.runFailableAsync(() -> { BooleanScope.executeExclusive(busy, () -> { - if (shellControl != null) { - shellControl.close(); - shellControl = null; + if (shellValidationContext != null) { + shellValidationContext.close(); + shellValidationContext = null; } - shellControl = newValue.getStore().control(); - shellControl.withoutLicenseCheck(); - shellControl.start(); - var a = applicable.apply(entry.get().get(), shellControl); + shellValidationContext = new ShellValidationContext(newValue.getStore().control().withoutLicenseCheck().start()); + var a = applicable.apply(entry.get().get(), shellValidationContext.get()); Platform.runLater(() -> { if (a == null) { diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/vault_empty.md b/app/src/main/resources/io/xpipe/app/resources/misc/vault_empty.md index 8db34b601..f30f74891 100644 --- a/app/src/main/resources/io/xpipe/app/resources/misc/vault_empty.md +++ b/app/src/main/resources/io/xpipe/app/resources/misc/vault_empty.md @@ -9,7 +9,7 @@ See below on how to do this. By default, no categories are set to shared so that you have explicit control on what connections to commit. To have your connections of a category put inside your git repository, -you need to click on the `⚙️` icon (when hovering over the category) +you either need to right-click the category or click on the `⚙️` icon when hovering over the category in your `Connections` tab under the category overview on the left side. Then click on `Add to git repository` to sync the category and connections to your git repository. This will add all shareable connections to the git repository. diff --git a/core/src/main/java/io/xpipe/core/store/ShellStore.java b/core/src/main/java/io/xpipe/core/store/ShellStore.java index b64ad2bbf..4574789d3 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/ShellStore.java @@ -3,7 +3,7 @@ package io.xpipe.core.store; import io.xpipe.core.process.ProcessControl; import io.xpipe.core.process.ShellControl; -public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore { +public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore { @Override default FileSystem createFileSystem() { @@ -17,12 +17,16 @@ public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore ShellControl control(); @Override - default void validate() throws Exception { - var c = control(); + default void validate(ShellValidationContext context) throws Exception { + var c = context.get(); if (!isInStorage()) { c.withoutLicenseCheck(); } - try (ShellControl pc = c.start()) {} } + + @Override + default ShellValidationContext createContext() throws Exception { + return new ShellValidationContext(control().start()); + } } diff --git a/core/src/main/java/io/xpipe/core/store/ShellValidationContext.java b/core/src/main/java/io/xpipe/core/store/ShellValidationContext.java new file mode 100644 index 000000000..6c0207908 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/ShellValidationContext.java @@ -0,0 +1,22 @@ +package io.xpipe.core.store; + +import io.xpipe.core.process.ShellControl; +import lombok.Value; + +@Value +public class ShellValidationContext implements ValidationContext { + + ShellControl shellControl; + + @Override + public ShellControl get() { + return shellControl; + } + + @Override + public void close() { + try { + shellControl.close(); + } catch (Exception ignored) {} + } +} diff --git a/core/src/main/java/io/xpipe/core/store/ValidatableStore.java b/core/src/main/java/io/xpipe/core/store/ValidatableStore.java index f8519de81..48e1f2f2f 100644 --- a/core/src/main/java/io/xpipe/core/store/ValidatableStore.java +++ b/core/src/main/java/io/xpipe/core/store/ValidatableStore.java @@ -1,6 +1,6 @@ package io.xpipe.core.store; -public interface ValidatableStore extends DataStore { +public interface ValidatableStore> extends DataStore { /** * Performs a validation of this data store. @@ -18,5 +18,9 @@ public interface ValidatableStore extends DataStore { * * @throws Exception if any part of the validation went wrong */ - default void validate() throws Exception {} + default void validate(T context) throws Exception {} + + default T createContext() throws Exception { + return null; + } } diff --git a/core/src/main/java/io/xpipe/core/store/ValidationContext.java b/core/src/main/java/io/xpipe/core/store/ValidationContext.java new file mode 100644 index 000000000..690ab48b4 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/ValidationContext.java @@ -0,0 +1,8 @@ +package io.xpipe.core.store; + +public interface ValidationContext { + + T get(); + + void close(); +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanStoreAction.java index 717335ded..3e1a32636 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanStoreAction.java @@ -70,7 +70,7 @@ public class ScanStoreAction implements ActionProvider { @Override public void execute() { if (entry == null || entry.getStore() instanceof ShellStore) { - ScanAlert.showForShellStore(entry); + ScanAlert.showForShellStore(entry, null); } } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java index aa6dabf78..80c2e1650 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/store/ShellStoreProvider.java @@ -10,13 +10,14 @@ import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreUsageCategory; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.resources.SystemIcons; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.DataStoreFormatter; import io.xpipe.app.util.TerminalLauncher; +import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.store.ShellStore; import io.xpipe.ext.base.script.ScriptStore; - import javafx.beans.property.BooleanProperty; import javafx.beans.value.ObservableValue; @@ -29,11 +30,19 @@ public interface ShellStoreProvider extends DataStoreProvider { public void execute() throws Exception { var replacement = ProcessControlProvider.get().replace(entry.ref()); ShellStore store = replacement.getStore().asNeeded(); + var control = ScriptStore.controlWithDefaultScripts(store.control()); + control.onInit(sc -> { + if (entry.getStorePersistentState() instanceof ShellStoreState shellStoreState && shellStoreState.getShellDialect() == null) { + var found = SystemIcons.detectForSystem(sc); + if (found.isPresent()) { + entry.setIcon(found.get().getIconName(), false); + } + } + }); TerminalLauncher.open( replacement.get(), DataStorage.get().getStoreEntryDisplayName(replacement.get()), - null, - ScriptStore.controlWithDefaultScripts(store.control())); + null, control); } }; }