diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java index 83445dbc7..3c56e5c6e 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java @@ -18,7 +18,7 @@ public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange { if (!(e.getStore() instanceof ShellStore shellStore)) { throw new BeaconClientException("Not a shell connection"); } - try (var sc = shellStore.control().start()) { + try (var sc = shellStore.shellFunction().control().start()) { TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc); } return Response.builder().build(); diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java index 365aae364..5700b2627 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java @@ -25,7 +25,7 @@ public class ShellStartExchangeImpl extends ShellStartExchange { var existing = AppBeaconServer.get().getCache().getShellSessions().stream() .filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)) .findFirst(); - var control = (existing.isPresent() ? existing.get().getControl() : s.control()); + var control = (existing.isPresent() ? existing.get().getControl() : s.shellFunction().control().start()); control.setNonInteractive(); control.start(); diff --git a/app/src/main/java/io/xpipe/app/ext/LocalStore.java b/app/src/main/java/io/xpipe/app/ext/LocalStore.java index 328c0077e..dd4d4d98d 100644 --- a/app/src/main/java/io/xpipe/app/ext/LocalStore.java +++ b/app/src/main/java/io/xpipe/app/ext/LocalStore.java @@ -9,8 +9,6 @@ import io.xpipe.core.util.JacksonizedValue; import com.fasterxml.jackson.annotation.JsonTypeName; -import java.util.Optional; - @JsonTypeName("local") public class LocalStore extends JacksonizedValue implements NetworkTunnelStore, ShellStore, StatefulDataStore { @@ -20,18 +18,22 @@ public class LocalStore extends JacksonizedValue return ShellStoreState.class; } - @Override - public ShellControl parentControl() { - var pc = ProcessControlProvider.get().createLocalProcessControl(true); - pc.withSourceStore(this); - pc.withShellStateInit(this); - pc.withShellStateFail(this); - return pc; + public ShellControl control(ShellControl parent) { + return parent; } @Override - public ShellControl control(ShellControl parent) { - return parent; + public ShellControlFunction shellFunction() { + return new ShellControlFunction() { + @Override + public ShellControl control() throws Exception { + var pc = ProcessControlProvider.get().createLocalProcessControl(true); + pc.withSourceStore(LocalStore.this); + pc.withShellStateInit(LocalStore.this); + pc.withShellStateFail(LocalStore.this); + return pc; + } + }; } @Override diff --git a/app/src/main/java/io/xpipe/app/ext/ShellControlFunction.java b/app/src/main/java/io/xpipe/app/ext/ShellControlFunction.java new file mode 100644 index 000000000..08dd5978f --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/ShellControlFunction.java @@ -0,0 +1,8 @@ +package io.xpipe.app.ext; + +import io.xpipe.core.process.ShellControl; + +public interface ShellControlFunction { + + ShellControl control() throws Exception; +} diff --git a/app/src/main/java/io/xpipe/app/ext/ShellControlParentFunction.java b/app/src/main/java/io/xpipe/app/ext/ShellControlParentFunction.java new file mode 100644 index 000000000..5ccc87afb --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/ShellControlParentFunction.java @@ -0,0 +1,14 @@ +package io.xpipe.app.ext; + +import io.xpipe.core.process.ShellControl; + +public interface ShellControlParentFunction extends ShellControlFunction{ + + default ShellControl control() throws Exception { + return control(parentControl()); + } + + ShellControl control(ShellControl parent) throws Exception; + + ShellControl parentControl() throws Exception; +} diff --git a/app/src/main/java/io/xpipe/app/ext/ShellControlParentStoreFunction.java b/app/src/main/java/io/xpipe/app/ext/ShellControlParentStoreFunction.java new file mode 100644 index 000000000..901589f3f --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/ShellControlParentStoreFunction.java @@ -0,0 +1,18 @@ +package io.xpipe.app.ext; + +import io.xpipe.core.process.ShellControl; + +public interface ShellControlParentStoreFunction extends ShellControlFunction{ + + default ShellControl control() throws Exception { + return control(parentControl()); + } + + ShellControl control(ShellControl parent) throws Exception; + + default ShellControl parentControl() throws Exception { + return getParentStore().getOrStartSession(); + } + + ShellStore getParentStore(); +} diff --git a/app/src/main/java/io/xpipe/app/ext/ShellSession.java b/app/src/main/java/io/xpipe/app/ext/ShellSession.java index d1b9c27b2..e4e58f7ce 100644 --- a/app/src/main/java/io/xpipe/app/ext/ShellSession.java +++ b/app/src/main/java/io/xpipe/app/ext/ShellSession.java @@ -4,6 +4,7 @@ import io.xpipe.core.process.ShellControl; import io.xpipe.core.store.Session; import io.xpipe.core.store.SessionListener; +import io.xpipe.core.util.FailableSupplier; import lombok.Getter; import java.util.function.Supplier; @@ -11,10 +12,10 @@ import java.util.function.Supplier; @Getter public class ShellSession extends Session { - private final Supplier supplier; + private final FailableSupplier supplier; private final ShellControl shellControl; - public ShellSession(SessionListener listener, Supplier supplier) { + public ShellSession(SessionListener listener, FailableSupplier supplier) throws Exception { super(listener); this.supplier = supplier; this.shellControl = createControl(); @@ -35,7 +36,7 @@ public class ShellSession extends Session { } } - private ShellControl createControl() { + private ShellControl createControl() throws Exception { var pc = supplier.get(); pc.onStartupFail(shellControl -> { listener.onStateChange(false); diff --git a/app/src/main/java/io/xpipe/app/ext/ShellStore.java b/app/src/main/java/io/xpipe/app/ext/ShellStore.java index 45caf0e83..77676e5c2 100644 --- a/app/src/main/java/io/xpipe/app/ext/ShellStore.java +++ b/app/src/main/java/io/xpipe/app/ext/ShellStore.java @@ -17,12 +17,13 @@ public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore } startSessionIfNeeded(); - return getSession().getShellControl(); + return new StubShellControl(getSession().getShellControl()); } @Override - default ShellSession newSession() { - return new ShellSession(this, () -> control()); + default ShellSession newSession() throws Exception { + var func = shellFunction(); + return new ShellSession(this, () -> func.control()); } @Override @@ -31,21 +32,18 @@ public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore } @Override - default FileSystem createFileSystem() { - return new ConnectionFileSystem(control()); + default FileSystem createFileSystem() throws Exception { + var func = shellFunction(); + return new ConnectionFileSystem(func.control()); } - ShellControl parentControl(); - - ShellControl control(ShellControl parent); - - default ShellControl control() { - return control(parentControl()); - } + ShellControlFunction shellFunction(); @Override default ShellValidationContext validate(ShellValidationContext context) throws Exception { - var c = control(context.get()); + var func = shellFunction(); + var c = func instanceof ShellControlParentStoreFunction s ? s.control(s.getParentStore().getOrStartSession()) : + func instanceof ShellControlParentFunction p ? p.control(p.parentControl()) : func.control(); if (!isInStorage()) { c.withoutLicenseCheck(); } @@ -54,6 +52,8 @@ public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore @Override default ShellValidationContext createContext() throws Exception { - return new ShellValidationContext(parentControl().start()); + var func = shellFunction(); + return func instanceof ShellControlParentStoreFunction s ? new ShellValidationContext(s.getParentStore().getOrStartSession()) : + func instanceof ShellControlParentFunction p ? new ShellValidationContext(p.parentControl()) : null; } } diff --git a/app/src/main/java/io/xpipe/app/ext/StubShellControl.java b/app/src/main/java/io/xpipe/app/ext/StubShellControl.java new file mode 100644 index 000000000..65bf18a2a --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/StubShellControl.java @@ -0,0 +1,13 @@ +package io.xpipe.app.ext; + +import io.xpipe.core.process.ShellControl; + +public class StubShellControl extends WrapperShellControl { + + public StubShellControl(ShellControl parent) { + super(parent); + } + + @Override + public void close() throws Exception {} +} diff --git a/app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java b/app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java new file mode 100644 index 000000000..4c071ae63 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java @@ -0,0 +1,331 @@ +package io.xpipe.app.ext; + +import io.xpipe.core.process.*; +import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.FilePath; +import io.xpipe.core.util.FailableConsumer; +import lombok.Getter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Function; + +public class WrapperShellControl implements ShellControl { + + @Getter + protected final ShellControl parent; + + public WrapperShellControl( + ShellControl parent) { + this.parent = parent; + } + + @Override + public Optional getParentControl() { + return parent.getParentControl(); + } + + @Override + public ShellTtyState getTtyState() { + return parent.getTtyState(); + } + + @Override + public void setNonInteractive() { + parent.setNonInteractive(); + } + + @Override + public boolean isInteractive() { + return parent.isInteractive(); + } + + @Override + public ElevationHandler getElevationHandler() { + return parent.getElevationHandler(); + } + + @Override + public void setElevationHandler(ElevationHandler ref) { + parent.setElevationHandler(ref); + } + + @Override + public List getExitUuids() { + return parent.getExitUuids(); + } + + @Override + public void setWorkingDirectory(WorkingDirectoryFunction workingDirectory) { + parent.setWorkingDirectory(workingDirectory); + } + + @Override + public Optional getSourceStore() { + return parent.getSourceStore(); + } + + @Override + public ShellControl withSourceStore(DataStore store) { + return parent.withSourceStore(store); + } + + @Override + public List getInitCommands() { + return parent.getInitCommands(); + } + + @Override + public ParentSystemAccess getParentSystemAccess() { + return parent.getParentSystemAccess(); + } + + @Override + public void setParentSystemAccess(ParentSystemAccess access) { + parent.setParentSystemAccess(access); + } + + @Override + public ParentSystemAccess getLocalSystemAccess() { + return parent.getLocalSystemAccess(); + } + + @Override + public boolean isLocal() { + return parent.isLocal(); + } + + @Override + public ShellControl getMachineRootSession() { + return parent.getMachineRootSession(); + } + + @Override + public ShellControl withoutLicenseCheck() { + return parent.withoutLicenseCheck(); + } + + @Override + public String getOsName() { + return parent.getOsName(); + } + + @Override + public boolean isLicenseCheck() { + return parent.isLicenseCheck(); + } + + @Override + public ReentrantLock getLock() { + return parent.getLock(); + } + + @Override + public ShellDialect getOriginalShellDialect() { + return parent.getOriginalShellDialect(); + } + + @Override + public void setOriginalShellDialect(ShellDialect dialect) { + parent.setOriginalShellDialect(dialect); + } + + @Override + public ShellControl onInit(FailableConsumer pc) { + return parent.onInit(pc); + } + + @Override + public ShellControl onExit(Consumer pc) { + return parent.onExit(pc); + } + + @Override + public ShellControl onKill(Runnable pc) { + return parent.onKill(pc); + } + + @Override + public ShellControl onStartupFail(Consumer t) { + return parent.onStartupFail(t); + } + + @Override + public UUID getUuid() { + return parent.getUuid(); + } + + @Override + public ShellControl withExceptionConverter(ExceptionConverter converter) { + return parent.withExceptionConverter(converter); + } + + @Override + public void resetData() { + parent.resetData(); + } + + @Override + public String prepareTerminalOpen(TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory) throws Exception { + return parent.prepareTerminalOpen(config, workingDirectory); + } + + @Override + public void closeStdin() throws IOException { + parent.closeStdin(); + } + + @Override + public boolean isStdinClosed() { + return parent.isStdinClosed(); + } + + @Override + public boolean isRunning() { + return parent.isRunning(); + } + + @Override + public ShellDialect getShellDialect() { + return parent.getShellDialect(); + } + + @Override + public void writeLine(String line) throws IOException { + parent.writeLine(line); + } + + @Override + public void writeLine(String line, boolean log) throws IOException { + parent.writeLine(line, log); + } + + @Override + public void write(byte[] b) throws IOException { + parent.write(b); + } + + @Override + public void close() throws Exception { + parent.close(); + } + + @Override + public void shutdown() throws Exception { + parent.shutdown(); + } + + @Override + public void kill() { + parent.kill(); + } + + @Override + public ShellControl start() throws Exception { + return parent.start(); + } + + @Override + public InputStream getStdout() { + return parent.getStdout(); + } + + @Override + public OutputStream getStdin() { + return parent.getStdin(); + } + + @Override + public InputStream getStderr() { + return parent.getStderr(); + } + + @Override + public Charset getCharset() { + return parent.getCharset(); + } + + @Override + public ShellControl withErrorFormatter(Function formatter) { + return parent.withErrorFormatter(formatter); + } + + @Override + public String prepareIntermediateTerminalOpen( + TerminalInitFunction content, TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory + ) throws Exception { + return parent.prepareIntermediateTerminalOpen(content, config, workingDirectory); + } + + @Override + public FilePath getSystemTemporaryDirectory() { + return parent.getSystemTemporaryDirectory(); + } + + @Override + public ShellControl withSecurityPolicy(ShellSecurityPolicy policy) { + return parent.withSecurityPolicy(policy); + } + + @Override + public ShellSecurityPolicy getEffectiveSecurityPolicy() { + return parent.getEffectiveSecurityPolicy(); + } + + @Override + public String buildElevatedCommand(CommandConfiguration input, String prefix, UUID requestId, CountDown countDown) throws Exception { + return parent.buildElevatedCommand(input, prefix, requestId, countDown); + } + + @Override + public void restart() throws Exception { + parent.restart(); + } + + @Override + public OsType.Any getOsType() { + return parent.getOsType(); + } + + @Override + public ShellControl elevated(ElevationFunction elevationFunction) { + return parent.elevated(elevationFunction); + } + + @Override + public ShellControl withInitSnippet(ShellInitCommand snippet) { + return parent.withInitSnippet(snippet); + } + + @Override + public ShellControl subShell(ShellOpenFunction command, ShellOpenFunction terminalCommand) { + return parent.subShell(command, terminalCommand); + } + + @Override + public ShellControl singularSubShell(ShellOpenFunction command) { + return parent.singularSubShell(command); + } + + @Override + public void cd(String directory) throws Exception { + parent.cd(directory); + } + + @Override + public CommandControl command(CommandBuilder builder) { + return parent.command(builder); + } + + @Override + public void exitAndWait() throws IOException { + parent.exitAndWait(); + } +} diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index cba814cee..c60993750 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -41,7 +41,7 @@ public class TerminalCategory extends AppPrefsCategory { var term = AppPrefs.get().terminalType().getValue(); if (term != null) { TerminalLauncher.open( - "Test", new LocalStore().control().command("echo Test")); + "Test", new LocalStore().shellFunction().control().command("echo Test")); } }); }))) diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index 4bf7a63a7..88fb315f9 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -79,7 +79,7 @@ public class AppInstaller { @Override public void installLocal(Path file) throws Exception { - var shellProcessControl = new LocalStore().control().start(); + var shellProcessControl = new LocalStore().shellFunction().control().start(); var exec = (AppProperties.get().isDevelopmentEnvironment() ? Path.of(XPipeInstallation.getLocalDefaultInstallationBasePath()) : XPipeInstallation.getCurrentInstallationBasePath()) diff --git a/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java b/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java index affc965fa..833a0874b 100644 --- a/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java @@ -31,7 +31,7 @@ public class ChocoUpdater extends UpdateHandler { } public AvailableRelease refreshUpdateCheckImpl() throws Exception { - try (var sc = new LocalStore().control().start()) { + try (var sc = new LocalStore().shellFunction().control().start()) { var latest = sc.executeSimpleStringCommand("choco outdated -r --nocolor") .lines() .filter(s -> s.startsWith("xpipe")) diff --git a/app/src/main/java/io/xpipe/app/util/ScanDialog.java b/app/src/main/java/io/xpipe/app/util/ScanDialog.java index 8043e2f2b..ca3d215c2 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanDialog.java +++ b/app/src/main/java/io/xpipe/app/util/ScanDialog.java @@ -167,7 +167,7 @@ class ScanDialog extends DialogComp { } shellValidationContext = new ShellValidationContext( - newValue.getStore().control().withoutLicenseCheck().start()); + newValue.getStore().shellFunction().control().withoutLicenseCheck().start()); // Handle window close while connection is established if (!window.isShowing()) { diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java index a48fe484d..57cbc4b70 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java @@ -1,6 +1,7 @@ package io.xpipe.app.util; import io.xpipe.app.ext.LocalStore; +import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStoreSecret; @@ -180,7 +181,7 @@ public interface SecretRetrievalStrategy { throw ErrorEvent.expected(new IllegalStateException("No custom command specified")); } - try (var cc = new LocalStore().control().command(command).start()) { + try (var cc = ProcessControlProvider.get().createLocalProcessControl(true).command(command).start()) { return new SecretQueryResult( InPlaceSecretValue.of(cc.readStdoutOrThrow()), SecretQueryState.NORMAL); } catch (Exception ex) { diff --git a/core/src/main/java/io/xpipe/core/store/FileSystemStore.java b/core/src/main/java/io/xpipe/core/store/FileSystemStore.java index 1c8f01460..0eb636efa 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystemStore.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystemStore.java @@ -2,5 +2,5 @@ package io.xpipe.core.store; public interface FileSystemStore extends DataStore { - FileSystem createFileSystem(); + FileSystem createFileSystem() throws Exception; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java b/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java index ec7a986b3..e97f58954 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java @@ -33,7 +33,7 @@ public class RunScriptActionMenu implements ActionProvider { @Override public void execute() throws Exception { - try (var sc = shellStore.getStore().control().start()) { + try (var sc = shellStore.getStore().shellFunction().control().start()) { var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc); TerminalLauncher.open( shellStore.getEntry(), @@ -86,7 +86,7 @@ public class RunScriptActionMenu implements ActionProvider { @Override public void execute() throws Exception { - try (var sc = shellStore.getStore().control().start()) { + try (var sc = shellStore.getStore().shellFunction().control().start()) { var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc); sc.command(script).execute(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/SampleStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/SampleStoreAction.java index fda183e09..50671abd6 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/SampleStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/SampleStoreAction.java @@ -66,7 +66,7 @@ public class SampleStoreAction implements ActionProvider { public void execute() throws Exception { var docker = new LocalStore(); // Start a shell control from the docker connection store - try (ShellControl sc = docker.control().start()) { + try (ShellControl sc = docker.shellFunction().control().start()) { // Once we are here, the shell connection is initialized and we can query all kinds of information // Query the detected shell dialect, e.g. cmd, powershell, sh, bash, etc. 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 866dc6997..9fb01bf11 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 @@ -31,7 +31,7 @@ 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()); + var control = ScriptStore.controlWithDefaultScripts(store.shellFunction().control()); control.onInit(sc -> { if (entry.getStorePersistentState() instanceof ShellStoreState shellStoreState && shellStoreState.getShellDialect() == null) {