This commit is contained in:
crschnick 2024-10-22 17:21:31 +00:00
parent 4b15af2f17
commit 64d4a8bd82
19 changed files with 428 additions and 40 deletions

View file

@ -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();

View file

@ -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();

View file

@ -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<ShellStoreState> {
@ -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

View file

@ -0,0 +1,8 @@
package io.xpipe.app.ext;
import io.xpipe.core.process.ShellControl;
public interface ShellControlFunction {
ShellControl control() throws Exception;
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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<ShellControl> supplier;
private final FailableSupplier<ShellControl> supplier;
private final ShellControl shellControl;
public ShellSession(SessionListener listener, Supplier<ShellControl> supplier) {
public ShellSession(SessionListener listener, FailableSupplier<ShellControl> 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);

View file

@ -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;
}
}

View file

@ -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 {}
}

View file

@ -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<ShellControl> 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<UUID> getExitUuids() {
return parent.getExitUuids();
}
@Override
public void setWorkingDirectory(WorkingDirectoryFunction workingDirectory) {
parent.setWorkingDirectory(workingDirectory);
}
@Override
public Optional<DataStore> getSourceStore() {
return parent.getSourceStore();
}
@Override
public ShellControl withSourceStore(DataStore store) {
return parent.withSourceStore(store);
}
@Override
public List<ShellInitCommand> 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<ShellControl, Exception> pc) {
return parent.onInit(pc);
}
@Override
public ShellControl onExit(Consumer<ShellControl> pc) {
return parent.onExit(pc);
}
@Override
public ShellControl onKill(Runnable pc) {
return parent.onKill(pc);
}
@Override
public ShellControl onStartupFail(Consumer<Throwable> 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<String, String> 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();
}
}

View file

@ -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"));
}
});
})))

View file

@ -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())

View file

@ -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"))

View file

@ -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()) {

View file

@ -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) {

View file

@ -2,5 +2,5 @@ package io.xpipe.core.store;
public interface FileSystemStore extends DataStore {
FileSystem createFileSystem();
FileSystem createFileSystem() throws Exception;
}

View file

@ -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();
}

View file

@ -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.

View file

@ -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) {