mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-23 08:00:56 -04:00
Rework validation and state management
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<DataStoreEntry, Boolean> consumer;
|
||||
CreationConsumer consumer;
|
||||
Property<DataStoreProvider> provider;
|
||||
ObjectProperty<DataStore> store;
|
||||
Predicate<DataStoreProvider> filter;
|
||||
@@ -67,7 +65,7 @@ public class StoreCreationComp extends DialogComp {
|
||||
|
||||
public StoreCreationComp(
|
||||
Stage window,
|
||||
BiConsumer<DataStoreEntry, Boolean> consumer,
|
||||
CreationConsumer consumer,
|
||||
Property<DataStoreProvider> provider,
|
||||
ObjectProperty<DataStore> store,
|
||||
Predicate<DataStoreProvider> 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<DataStoreProvider> filter,
|
||||
BiConsumer<DataStoreEntry, Boolean> 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(() -> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<T extends DataStore> 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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<SystemIcon> SYSTEM_ICONS = new ArrayList<>();
|
||||
|
||||
@@ -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 <T extends ValidationContext<?>> void validate() {
|
||||
try {
|
||||
validateOrThrow();
|
||||
validateOrThrow(true);
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
|
||||
public void validateOrThrow() throws Throwable {
|
||||
public <T extends ValidationContext<?>> void validate(T context) {
|
||||
try {
|
||||
validateOrThrow(context);
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ValidationContext<?>> 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<T>) 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 <T> ValidationContext<?> validateOrThrow(boolean close) throws Throwable {
|
||||
if (store == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
store.checkComplete();
|
||||
incrementBusyCounter();
|
||||
if (store instanceof ValidatableStore<?> l) {
|
||||
ValidationContext<T> context = (ValidationContext<T>) l.createContext();
|
||||
try {
|
||||
((ValidatableStore<ValidationContext<T>>) 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;
|
||||
|
||||
@@ -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<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> applicable) {
|
||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> 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<ScanProvider.ScanOperation> selected =
|
||||
new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final BooleanProperty busy = new SimpleBooleanProperty();
|
||||
private ShellControl shellControl;
|
||||
private ShellValidationContext shellValidationContext;
|
||||
|
||||
private Dialog(
|
||||
Stage window,
|
||||
DataStoreEntryRef<ShellStore> entry,
|
||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> applicable) {
|
||||
BiFunction<DataStoreEntry, ShellControl, List<ScanProvider.ScanOperation>> 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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<ShellValidationContext> {
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 shellControl;
|
||||
|
||||
@Override
|
||||
public ShellControl get() {
|
||||
return shellControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
shellControl.close();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.xpipe.core.store;
|
||||
|
||||
public interface ValidatableStore extends DataStore {
|
||||
public interface ValidatableStore<T extends ValidationContext<?>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.xpipe.core.store;
|
||||
|
||||
public interface ValidationContext<T> {
|
||||
|
||||
T get();
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user