From de70b0d5b0a8d1b7b1c22b6352aab74a0b8b3e51 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Sun, 27 Nov 2022 14:59:36 +0100 Subject: [PATCH] Refactor --- .../main/java/io/xpipe/api/DataStores.java | 4 +- ...onnection.java => XPipeApiConnection.java} | 36 +++--- .../io/xpipe/api/impl/DataSourceImpl.java | 22 ++-- .../api/impl/DataTableAccumulatorImpl.java | 10 +- .../java/io/xpipe/api/impl/DataTableImpl.java | 6 +- .../java/io/xpipe/api/impl/DataTextImpl.java | 4 +- .../xpipe/api/util/XPipeDaemonController.java | 46 -------- .../test/java/io/xpipe/api/test/ApiTest.java | 6 +- .../java/io/xpipe/beacon/BeaconClient.java | 15 ++- .../xpipe/beacon/BeaconDaemonController.java | 88 ++++++++++++++ .../java/io/xpipe/beacon/BeaconServer.java | 107 +++++------------- .../java/io/xpipe/beacon/NamedFunction.java | 37 ++++++ .../exchange/NamedFunctionExchange.java | 9 +- beacon/src/main/java/module-info.java | 1 + .../java/io/xpipe/core/impl/FileNames.java | 8 ++ .../core/process/ShellProcessControl.java | 6 + .../java/io/xpipe/core/process/ShellType.java | 2 +- .../io/xpipe/core/store/MachineStore.java | 2 +- .../java/io/xpipe/core/util/Proxyable.java | 21 ++++ .../io/xpipe/core/util/XPipeInstallation.java | 98 ++++++++++++---- .../io/xpipe/extension/NamedFunction.java | 83 -------------- .../java/io/xpipe/extension/Proxyable.java | 8 -- .../java/io/xpipe/extension/XPipeProxy.java | 42 +++---- .../extension/XPipeServiceProviders.java | 1 - .../extension/util/DaemonExtensionTest.java | 6 +- extension/src/main/java/module-info.java | 3 +- 26 files changed, 344 insertions(+), 327 deletions(-) rename api/src/main/java/io/xpipe/api/connector/{XPipeConnection.java => XPipeApiConnection.java} (79%) delete mode 100644 api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java create mode 100644 beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java create mode 100644 beacon/src/main/java/io/xpipe/beacon/NamedFunction.java create mode 100644 core/src/main/java/io/xpipe/core/util/Proxyable.java delete mode 100644 extension/src/main/java/io/xpipe/extension/NamedFunction.java delete mode 100644 extension/src/main/java/io/xpipe/extension/Proxyable.java diff --git a/api/src/main/java/io/xpipe/api/DataStores.java b/api/src/main/java/io/xpipe/api/DataStores.java index 4f1e353e..c8720401 100644 --- a/api/src/main/java/io/xpipe/api/DataStores.java +++ b/api/src/main/java/io/xpipe/api/DataStores.java @@ -1,6 +1,6 @@ package io.xpipe.api; -import io.xpipe.api.connector.XPipeConnection; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.api.util.QuietDialogHandler; import io.xpipe.beacon.exchange.cli.StoreAddExchange; import io.xpipe.core.store.DataStore; @@ -10,7 +10,7 @@ import java.util.Map; public class DataStores { public static void addNamedStore(DataStore store, String name) { - XPipeConnection.execute(con -> { + XPipeApiConnection.execute(con -> { var req = StoreAddExchange.Request.builder() .storeInput(store) .name(name) diff --git a/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java b/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java similarity index 79% rename from api/src/main/java/io/xpipe/api/connector/XPipeConnection.java rename to api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java index b1003741..9f2168e4 100644 --- a/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java +++ b/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java @@ -1,23 +1,27 @@ package io.xpipe.api.connector; -import io.xpipe.beacon.*; +import io.xpipe.beacon.BeaconClient; +import io.xpipe.beacon.BeaconConnection; +import io.xpipe.beacon.BeaconException; +import io.xpipe.beacon.BeaconServer; import io.xpipe.beacon.exchange.cli.DialogExchange; import io.xpipe.core.dialog.DialogReference; +import io.xpipe.core.util.XPipeInstallation; import java.util.Optional; -public final class XPipeConnection extends BeaconConnection { +public final class XPipeApiConnection extends BeaconConnection { - private XPipeConnection() {} + private XPipeApiConnection() {} - public static XPipeConnection open() { - var con = new XPipeConnection(); + public static XPipeApiConnection open() { + var con = new XPipeApiConnection(); con.constructSocket(); return con; } public static void finishDialog(DialogReference reference) { - try (var con = new XPipeConnection()) { + try (var con = new XPipeApiConnection()) { con.constructSocket(); var element = reference.getStart(); while (true) { @@ -41,7 +45,7 @@ public final class XPipeConnection extends BeaconConnection { } public static void execute(Handler handler) { - try (var con = new XPipeConnection()) { + try (var con = new XPipeApiConnection()) { con.constructSocket(); handler.handle(con); } catch (BeaconException e) { @@ -52,7 +56,7 @@ public final class XPipeConnection extends BeaconConnection { } public static T execute(Mapper mapper) { - try (var con = new XPipeConnection()) { + try (var con = new XPipeApiConnection()) { con.constructSocket(); return mapper.handle(con); } catch (BeaconException e) { @@ -73,7 +77,10 @@ public final class XPipeConnection extends BeaconConnection { } catch (InterruptedException ignored) { } - var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build()); + var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder() + .version("?") + .language("Java") + .build()); if (s.isPresent()) { return s; } @@ -114,17 +121,18 @@ public final class XPipeConnection extends BeaconConnection { } try { - beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build()); + beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder() + .version("?") + .language("Java") + .build()); } catch (Exception ex) { throw new BeaconException("Unable to connect to running xpipe daemon", ex); } } private void start() throws Exception { - if (BeaconServer.tryStart() == null) { - throw new UnsupportedOperationException("Unable to determine xpipe daemon launch command"); - } - ; + var installation = XPipeInstallation.getDefaultInstallationBasePath(); + BeaconServer.start(installation); } @FunctionalInterface diff --git a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java index 42a640a3..1994633a 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java @@ -2,7 +2,7 @@ package io.xpipe.api.impl; import io.xpipe.api.DataSource; import io.xpipe.api.DataSourceConfig; -import io.xpipe.api.connector.XPipeConnection; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.beacon.exchange.*; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceReference; @@ -25,7 +25,7 @@ public abstract class DataSourceImpl implements DataSource { } public static DataSource get(DataSourceReference ds) { - return XPipeConnection.execute(con -> { + return XPipeApiConnection.execute(con -> { var req = QueryDataSourceExchange.Request.builder().ref(ds).build(); QueryDataSourceExchange.Response res = con.performSimpleExchange(req); var config = new DataSourceConfig(res.getProvider(), res.getConfig()); @@ -53,7 +53,7 @@ public abstract class DataSourceImpl implements DataSource { public static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource source) { var startReq = AddSourceExchange.Request.builder().source(source).target(id).build(); - var returnedId = XPipeConnection.execute(con -> { + var returnedId = XPipeApiConnection.execute(con -> { AddSourceExchange.Response r = con.performSimpleExchange(startReq); return r.getId(); }); @@ -64,7 +64,7 @@ public abstract class DataSourceImpl implements DataSource { public static DataSource create(DataSourceId id, String type, DataStore store) { if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) { - var res = XPipeConnection.execute(con -> { + var res = XPipeApiConnection.execute(con -> { var req = StoreStreamExchange.Request.builder().build(); StoreStreamExchange.Response r = con.performOutputExchange(req, out -> { try (InputStream inputStream = s.openInput()) { @@ -83,20 +83,20 @@ public abstract class DataSourceImpl implements DataSource { .target(id) .configureAll(false) .build(); - var startRes = XPipeConnection.execute(con -> { + var startRes = XPipeApiConnection.execute(con -> { ReadExchange.Response r = con.performSimpleExchange(startReq); return r; }); var configInstance = startRes.getConfig(); - XPipeConnection.finishDialog(configInstance); + XPipeApiConnection.finishDialog(configInstance); var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest(); return get(ref); } public static DataSource create(DataSourceId id, String type, InputStream in) { - var res = XPipeConnection.execute(con -> { + var res = XPipeApiConnection.execute(con -> { var req = StoreStreamExchange.Request.builder().build(); StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out)); return r; @@ -110,13 +110,13 @@ public abstract class DataSourceImpl implements DataSource { .target(id) .configureAll(false) .build(); - var startRes = XPipeConnection.execute(con -> { + var startRes = XPipeApiConnection.execute(con -> { ReadExchange.Response r = con.performSimpleExchange(startReq); return r; }); var configInstance = startRes.getConfig(); - XPipeConnection.finishDialog(configInstance); + XPipeApiConnection.finishDialog(configInstance); var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest(); return get(ref); @@ -124,7 +124,7 @@ public abstract class DataSourceImpl implements DataSource { @Override public void forwardTo(DataSource target) { - XPipeConnection.execute(con -> { + XPipeApiConnection.execute(con -> { var req = ForwardExchange.Request.builder() .source(DataSourceReference.id(sourceId)) .target(DataSourceReference.id(target.getId())) @@ -135,7 +135,7 @@ public abstract class DataSourceImpl implements DataSource { @Override public void appendTo(DataSource target) { - XPipeConnection.execute(con -> { + XPipeApiConnection.execute(con -> { var req = ForwardExchange.Request.builder() .source(DataSourceReference.id(sourceId)) .target(DataSourceReference.id(target.getId())) diff --git a/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java index 76fe5450..8b28837e 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableAccumulatorImpl.java @@ -3,7 +3,7 @@ package io.xpipe.api.impl; import io.xpipe.api.DataSource; import io.xpipe.api.DataTable; import io.xpipe.api.DataTableAccumulator; -import io.xpipe.api.connector.XPipeConnection; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.api.util.TypeDescriptor; import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.exchange.ReadExchange; @@ -22,7 +22,7 @@ import java.nio.charset.StandardCharsets; public class DataTableAccumulatorImpl implements DataTableAccumulator { - private final XPipeConnection connection; + private final XPipeApiConnection connection; private final TupleType type; private int rows; private TupleType writtenDescriptor; @@ -30,7 +30,7 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator { public DataTableAccumulatorImpl(TupleType type) { this.type = type; - connection = XPipeConnection.open(); + connection = XPipeApiConnection.open(); connection.sendRequest(StoreStreamExchange.Request.builder().build()); bodyOutput = connection.sendBody(); } @@ -52,12 +52,12 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator { .provider("xpbt") .configureAll(false) .build(); - ReadExchange.Response response = XPipeConnection.execute(con -> { + ReadExchange.Response response = XPipeApiConnection.execute(con -> { return con.performSimpleExchange(req); }); var configInstance = response.getConfig(); - XPipeConnection.finishDialog(configInstance); + XPipeApiConnection.finishDialog(configInstance); return DataSource.get(DataSourceReference.id(id)).asTable(); } diff --git a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java index 5c8b3a4c..4b68c2df 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java @@ -2,7 +2,7 @@ package io.xpipe.api.impl; import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataTable; -import io.xpipe.api.connector.XPipeConnection; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.beacon.BeaconConnection; import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.exchange.api.QueryTableDataExchange; @@ -55,7 +55,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable { @Override public ArrayNode read(int maxRows) { List nodes = new ArrayList<>(); - XPipeConnection.execute(con -> { + XPipeApiConnection.execute(con -> { var req = QueryTableDataExchange.Request.builder() .ref(DataSourceReference.id(getId())) .maxRows(maxRows) @@ -83,7 +83,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable { private TupleNode node; { - connection = XPipeConnection.open(); + connection = XPipeApiConnection.open(); var req = QueryTableDataExchange.Request.builder() .ref(DataSourceReference.id(getId())) .maxRows(Integer.MAX_VALUE) diff --git a/api/src/main/java/io/xpipe/api/impl/DataTextImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTextImpl.java index 7d553cce..f4a2fbff 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTextImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTextImpl.java @@ -2,7 +2,7 @@ package io.xpipe.api.impl; import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataText; -import io.xpipe.api.connector.XPipeConnection; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.beacon.BeaconConnection; import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.exchange.api.QueryTextDataExchange; @@ -62,7 +62,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText { private String nextValue; { - connection = XPipeConnection.open(); + connection = XPipeApiConnection.open(); var req = QueryTextDataExchange.Request.builder() .ref(DataSourceReference.id(getId())) .maxLines(-1) diff --git a/api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java b/api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java deleted file mode 100644 index 59325f1b..00000000 --- a/api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.xpipe.api.util; - -import io.xpipe.api.connector.XPipeConnection; -import io.xpipe.beacon.BeaconClient; -import io.xpipe.beacon.BeaconServer; - -public class XPipeDaemonController { - - private static boolean alreadyStarted; - - public static void start() throws Exception { - if (BeaconServer.isRunning()) { - alreadyStarted = true; - return; - } - - Process process = null; - if ((process = BeaconServer.tryStartCustom()) != null) { - } else { - if ((process = BeaconServer.tryStart()) == null) { - throw new AssertionError(); - } - } - - XPipeConnection.waitForStartup(process).orElseThrow(); - if (!BeaconServer.isRunning()) { - throw new AssertionError(); - } - } - - public static void stop() throws Exception { - if (alreadyStarted) { - return; - } - - if (!BeaconServer.isRunning()) { - return; - } - - var client = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java API Test").build()); - if (!BeaconServer.tryStop(client)) { - throw new AssertionError(); - } - XPipeConnection.waitForShutdown(); - } -} diff --git a/api/src/test/java/io/xpipe/api/test/ApiTest.java b/api/src/test/java/io/xpipe/api/test/ApiTest.java index a7ed1377..e4c08018 100644 --- a/api/src/test/java/io/xpipe/api/test/ApiTest.java +++ b/api/src/test/java/io/xpipe/api/test/ApiTest.java @@ -1,6 +1,6 @@ package io.xpipe.api.test; -import io.xpipe.api.util.XPipeDaemonController; +import io.xpipe.beacon.BeaconDaemonController; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -8,11 +8,11 @@ public class ApiTest { @BeforeAll public static void setup() throws Exception { - XPipeDaemonController.start(); + BeaconDaemonController.start(); } @AfterAll public static void teardown() throws Exception { - XPipeDaemonController.stop(); + BeaconDaemonController.stop(); } } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index a3bb86d4..b69d85a2 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -12,6 +12,7 @@ import io.xpipe.beacon.exchange.MessageExchanges; import io.xpipe.beacon.exchange.data.ClientErrorMessage; import io.xpipe.beacon.exchange.data.ServerErrorMessage; import io.xpipe.core.process.CommandProcessControl; +import io.xpipe.core.store.ShellStore; import io.xpipe.core.util.JacksonMapper; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -47,12 +48,11 @@ public class BeaconClient implements AutoCloseable { @EqualsAndHashCode(callSuper = false) public static class CliClientInformation extends ClientInformation { - String version; int consoleWidth; @Override public String toDisplayString() { - return "X-Pipe CLI " + version; + return "X-Pipe CLI"; } } @@ -104,7 +104,16 @@ public class BeaconClient implements AutoCloseable { return client; } - public static BeaconClient connectGateway(CommandProcessControl control, GatewayClientInformation information) throws Exception { + public static BeaconClient connectProxy(ShellStore proxy) throws Exception { + var control = proxy.create().start(); + var command = control.command("xpipe beacon").start(); + return BeaconClient.connectGateway( + command, + BeaconClient.GatewayClientInformation.builder() + .build()); + } + + private static BeaconClient connectGateway(CommandProcessControl control, GatewayClientInformation information) throws Exception { var client = new BeaconClient(() -> {}, control.getStdout(), control.getStdin()); client.sendObject(JacksonMapper.newMapper().valueToTree(information)); return client; diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java b/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java new file mode 100644 index 00000000..078790a2 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java @@ -0,0 +1,88 @@ +package io.xpipe.beacon; + +import io.xpipe.core.util.XPipeInstallation; + +import java.io.IOException; + +public class BeaconDaemonController { + + private static boolean alreadyStarted; + + public static void start() throws Exception { + if (BeaconServer.isRunning()) { + alreadyStarted = true; + return; + } + + var custom = false; + Process process; + if ((process = BeaconServer.tryStartCustom()) != null) { + custom = true; + } else { + var defaultBase = XPipeInstallation.getDefaultInstallationBasePath(); + process = BeaconServer.start(defaultBase); + } + + waitForStartup(process, custom); + if (!BeaconServer.isRunning()) { + throw new AssertionError(); + } + } + + public static void stop() throws Exception { + if (alreadyStarted) { + return; + } + + if (!BeaconServer.isRunning()) { + return; + } + + var client = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java API Test").build()); + if (!BeaconServer.tryStop(client)) { + throw new AssertionError(); + } + waitForShutdown(); + } + + private static void waitForStartup(Process process, boolean custom) throws IOException { + for (int i = 0; i < 160; i++) { + if (process != null && !custom && !process.isAlive()) { + throw new IOException("Daemon start failed"); + } + + if (process != null && custom && !process.isAlive() && process.exitValue() != 0) { + throw new IOException("Custom launch command failed"); + } + + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + + var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder() + .version("?") + .language("Java") + .build()); + if (s.isPresent()) { + return; + } + } + + throw new IOException("Wait for daemon start up timed out"); + } + + private static void waitForShutdown() { + for (int i = 0; i < 40; i++) { + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + + var r = BeaconServer.isRunning(); + if (!r) { + return; + } + } + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java index 1db8270a..8394edcf 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java @@ -1,14 +1,15 @@ package io.xpipe.beacon; import io.xpipe.beacon.exchange.StopExchange; -import io.xpipe.core.process.OsType; +import io.xpipe.core.impl.FileNames; +import io.xpipe.core.impl.LocalStore; +import io.xpipe.core.process.ShellProcessControl; +import io.xpipe.core.util.XPipeInstallation; import lombok.experimental.UtilityClass; import java.io.BufferedReader; import java.io.InputStreamReader; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; /** * Contains basic functionality to start, communicate, and stop a remote beacon server. @@ -16,16 +17,8 @@ import java.util.Optional; @UtilityClass public class BeaconServer { - public static void main(String[] args) throws Exception { - if (tryStartCustom() == null) { - if (tryStart() == null) { - System.exit(1); - } - } - } - public static boolean isRunning() { - try (var socket = BeaconClient.connect(null)) { + try (var ignored = BeaconClient.connect(null)) { return true; } catch (Exception e) { return false; @@ -44,18 +37,14 @@ public class BeaconServer { return null; } - public static Process tryStart() throws Exception { - var daemonExecutable = getDaemonExecutable(); - if (daemonExecutable.isPresent()) { - var command = "\"" + daemonExecutable.get() + "\" --external " - + (BeaconConfig.getDaemonArguments() != null ? BeaconConfig.getDaemonArguments() : ""); - // Tell daemon that we launched from an external tool - Process process = Runtime.getRuntime().exec(command); - printDaemonOutput(process, command); - return process; - } - - return null; + public static Process start(String installationBase) throws Exception { + var daemonExecutable = getDaemonExecutable(installationBase); + // Tell daemon that we launched from an external tool + var command = "\"" + daemonExecutable + "\" --external " + + (BeaconConfig.getDaemonArguments() != null ? BeaconConfig.getDaemonArguments() : ""); + Process process = Runtime.getRuntime().exec(command); + printDaemonOutput(process, command); + return process; } private static void printDaemonOutput(Process proc, String command) { @@ -111,65 +100,21 @@ public class BeaconServer { return res.isSuccess(); } - private static Optional getDaemonBasePath(OsType type) { - Path base = null; - // Prepare for invalid XPIPE_HOME path value - try { - var environmentVariable = System.getenv("XPIPE_HOME"); - base = environmentVariable != null ? Path.of(environmentVariable) : null; - } catch (Exception ex) { - } - - if (base == null) { - if (type.equals(OsType.WINDOWS)) { - base = Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe"); + public static Path getDaemonExecutable(String installationBase) throws Exception { + try (ShellProcessControl pc = new LocalStore().create().start()) { + var debug = BeaconConfig.launchDaemonInDebugMode(); + if (!debug) { + return Path.of( + FileNames.join(installationBase, XPipeInstallation.getDaemonExecutablePath(pc.getOsType()))); } else { - base = Path.of("/opt/xpipe/"); + if (BeaconConfig.attachDebuggerToDaemon()) { + return Path.of(FileNames.join( + installationBase, XPipeInstallation.getDaemonDebugAttachScriptPath(pc.getOsType()))); + } else { + return Path.of(FileNames.join( + installationBase, XPipeInstallation.getDaemonDebugScriptPath(pc.getOsType()))); + } } - if (!Files.exists(base)) { - base = null; - } - } - - return Optional.ofNullable(base); - } - - public static Path getDaemonExecutableInBaseDirectory(OsType type) { - if (type.equals(OsType.WINDOWS)) { - return Path.of("app", "runtime", "bin", "xpiped.bat"); - } else { - return Path.of("app/bin/xpiped"); - } - } - - public static Optional getDaemonExecutable() { - var base = getDaemonBasePath(OsType.getLocal()).orElseThrow(); - var debug = BeaconConfig.launchDaemonInDebugMode(); - Path executable; - if (!debug) { - executable = getDaemonExecutableInBaseDirectory(OsType.getLocal()); - } else { - String scriptName = null; - if (BeaconConfig.attachDebuggerToDaemon()) { - scriptName = "xpiped_debug_attach"; - } else { - scriptName = "xpiped_debug"; - } - - if (System.getProperty("os.name").startsWith("Windows")) { - scriptName = scriptName + ".bat"; - } else { - scriptName = scriptName + ".sh"; - } - - executable = Path.of("app", "scripts", scriptName); - } - - Path file = base.resolve(executable); - if (Files.exists(file)) { - return Optional.of(file); - } else { - return Optional.empty(); } } } diff --git a/beacon/src/main/java/io/xpipe/beacon/NamedFunction.java b/beacon/src/main/java/io/xpipe/beacon/NamedFunction.java new file mode 100644 index 00000000..8ba15c16 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/NamedFunction.java @@ -0,0 +1,37 @@ +package io.xpipe.beacon; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.xpipe.beacon.exchange.NamedFunctionExchange; +import io.xpipe.core.util.Proxyable; +import lombok.Getter; +import lombok.SneakyThrows; + +import java.util.Arrays; + +@Getter +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public abstract class NamedFunction { + + @SneakyThrows + public T call() { + var proxyStore = Proxyable.getProxy(getProxyBase()); + if (proxyStore != null) { + var client = BeaconClient.connectProxy(proxyStore); + client.sendRequest( + NamedFunctionExchange.Request.builder().function(this).build()); + NamedFunctionExchange.Response response = client.receiveResponse(); + return (T) response.getReturnValue(); + } else { + return callLocal(); + } + } + + @SneakyThrows + protected Object getProxyBase() { + var first = Arrays.stream(getClass().getDeclaredFields()).findFirst().orElseThrow(); + first.setAccessible(true); + return first.get(this); + } + + public abstract T callLocal(); +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/NamedFunctionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/NamedFunctionExchange.java index bbfbdd33..1b2c485c 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/NamedFunctionExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/NamedFunctionExchange.java @@ -1,10 +1,9 @@ package io.xpipe.beacon.exchange; -import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.xpipe.beacon.NamedFunction; import io.xpipe.beacon.RequestMessage; import io.xpipe.beacon.ResponseMessage; import lombok.Builder; -import lombok.NonNull; import lombok.Value; import lombok.extern.jackson.Jacksonized; @@ -19,11 +18,7 @@ public class NamedFunctionExchange implements MessageExchange { @Builder @Value public static class Request implements RequestMessage { - @NonNull - String id; - - @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="type") - @NonNull Object[] arguments; + NamedFunction function; } @Jacksonized diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index 6aea1369..ab0131ea 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -25,6 +25,7 @@ module io.xpipe.beacon { requires static lombok; uses MessageExchange; + uses io.xpipe.beacon.NamedFunction; provides Module with BeaconJacksonModule; provides io.xpipe.beacon.exchange.MessageExchange with diff --git a/core/src/main/java/io/xpipe/core/impl/FileNames.java b/core/src/main/java/io/xpipe/core/impl/FileNames.java index ed6f051a..2d9c62a8 100644 --- a/core/src/main/java/io/xpipe/core/impl/FileNames.java +++ b/core/src/main/java/io/xpipe/core/impl/FileNames.java @@ -18,6 +18,14 @@ public class FileNames { return normalize(joined); } + public static String getParent(String file) { + return file.substring(0, file.length() - getFileName(file).length() - 1); + } + + public static boolean startsWith(String file, String start) { + return normalize(file).startsWith(normalize(start)); + } + public static String normalize(String file) { var backslash = file.contains("\\"); return backslash ? toWindows(file) : toUnix(file); diff --git a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java index 8fb1ab60..fa1dcd68 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java @@ -17,6 +17,12 @@ public interface ShellProcessControl extends ProcessControl { } } + default boolean executeBooleanSimpleCommand(String command) throws Exception { + try (CommandProcessControl c = command(command).start()) { + return c.discardAndCheckExit(); + } + } + default String executeSimpleCommand(ShellType type, String command) throws Exception { return executeSimpleCommand(type.switchTo(command)); } diff --git a/core/src/main/java/io/xpipe/core/process/ShellType.java b/core/src/main/java/io/xpipe/core/process/ShellType.java index e7df0a62..293616d1 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellType.java +++ b/core/src/main/java/io/xpipe/core/process/ShellType.java @@ -49,7 +49,7 @@ public interface ShellType { String createFileWriteCommand(String file); - List createFileExistsCommand(String file); + String createFileExistsCommand(String file); Charset determineCharset(ShellProcessControl control) throws Exception; diff --git a/core/src/main/java/io/xpipe/core/store/MachineStore.java b/core/src/main/java/io/xpipe/core/store/MachineStore.java index 64d80017..8868bf6a 100644 --- a/core/src/main/java/io/xpipe/core/store/MachineStore.java +++ b/core/src/main/java/io/xpipe/core/store/MachineStore.java @@ -37,7 +37,7 @@ public interface MachineStore extends FileSystemStore, ShellStore { @Override public default boolean exists(String file) throws Exception { - try (var pc = create().commandListFunction(proc -> proc.getShellType().createFileExistsCommand(proc.getOsType().normalizeFileName(file))) + try (var pc = create().commandFunction(proc -> proc.getShellType().createFileExistsCommand(proc.getOsType().normalizeFileName(file))) .start()) { return pc.discardAndCheckExit(); } diff --git a/core/src/main/java/io/xpipe/core/util/Proxyable.java b/core/src/main/java/io/xpipe/core/util/Proxyable.java new file mode 100644 index 00000000..de6afcba --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/Proxyable.java @@ -0,0 +1,21 @@ +package io.xpipe.core.util; + +import io.xpipe.core.store.ShellStore; + +public interface Proxyable { + + public static ShellStore getProxy(Object base) { + var proxy = base instanceof Proxyable p ? p.getProxy() : null; + return ShellStore.isLocal(proxy) ? null : proxy; + } + + public static boolean isRemote(Object base) { + if (base == null) { + throw new IllegalArgumentException("Proxy base is null"); + } + + return base instanceof Proxyable p && !ShellStore.isLocal(p.getProxy()); + } + + ShellStore getProxy(); +} diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index d7559b24..a0843bf4 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -1,43 +1,56 @@ package io.xpipe.core.util; import io.xpipe.core.impl.FileNames; +import io.xpipe.core.impl.LocalStore; import io.xpipe.core.process.CommandProcessControl; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellProcessControl; -import java.util.Optional; +import java.io.IOException; public class XPipeInstallation { - public static Optional queryInstallationVersion(ShellProcessControl p) throws Exception { - var executable = getInstallationExecutable(p); - if (executable.isEmpty()) { - return Optional.empty(); + public static String getInstallationBasePathForCLI(ShellProcessControl p, String cliExecutable) throws Exception { + var defaultInstallation = getDefaultInstallationBasePath(p); + if (p.getOsType().equals(OsType.LINUX) && cliExecutable.equals("/usr/bin/xpipe")) { + return defaultInstallation; } - try (CommandProcessControl c = p.command(executable.get() + " version").start()) { - return Optional.ofNullable(c.readOrThrow()); + if (FileNames.startsWith(cliExecutable, defaultInstallation)) { + return defaultInstallation; + } + + return FileNames.getParent(FileNames.getParent(cliExecutable)); + } + + public static String queryInstallationVersion(ShellProcessControl p, String exec) throws Exception { + try (CommandProcessControl c = p.command(exec + " version").start()) { + return c.readOrThrow(); } } - public static boolean containsCompatibleInstallation(ShellProcessControl p, String version) throws Exception { - var executable = getInstallationExecutable(p); + public static boolean containsCompatibleDefaultInstallation(ShellProcessControl p, String version) throws Exception { + var defaultBase = getDefaultInstallationBasePath(p); + var executable = getInstallationExecutable(p, defaultBase); if (executable.isEmpty()) { return false; } - try (CommandProcessControl c = p.command(executable.get() + " version").start()) { + try (CommandProcessControl c = p.command(executable + " version").start()) { return c.readOrThrow().equals(version); } } - public static Optional getInstallationExecutable(ShellProcessControl p) throws Exception { - var installation = getDefaultInstallationBasePath(p); - var executable = getDaemonExecutableInInstallationDirectory(p.getOsType()); + public static String getInstallationExecutable(ShellProcessControl p, String installation) throws Exception { + var executable = getDaemonExecutablePath(p.getOsType()); var file = FileNames.join(installation, executable); try (CommandProcessControl c = p.command(p.getShellType().createFileExistsCommand(file)).start()) { - return c.startAndCheckExit() ? Optional.of(file) : Optional.empty(); + if (!c.startAndCheckExit()) { + throw new IOException("File not found: " + file); + } + + return file; } } @@ -50,20 +63,57 @@ public class XPipeInstallation { } } - public static String getDefaultInstallationBasePath(ShellProcessControl p) throws Exception { - if (p.getOsType().equals(OsType.WINDOWS)) { - var base = p.executeSimpleCommand(p.getShellType().getPrintVariableCommand("LOCALAPPDATA")); - return FileNames.join(base, "X-Pipe"); - } else { - return "/opt/xpipe"; + public static String getDefaultInstallationBasePath() throws Exception { + try (ShellProcessControl pc = new LocalStore().create().start()) { + return getDefaultInstallationBasePath(pc); } } - public static String getDaemonExecutableInInstallationDirectory(OsType type) { - if (type.equals(OsType.WINDOWS)) { - return FileNames.join("app", "runtime", "bin", "xpiped.bat"); + public static String getDefaultInstallationBasePath(ShellProcessControl p) throws Exception { + var customHome = p.executeSimpleCommand(p.getShellType().getPrintVariableCommand("XPIPE_HOME")); + if (!customHome.isEmpty()) { + return customHome; + } + + String path = null; + if (p.getOsType().equals(OsType.WINDOWS)) { + var base = p.executeSimpleCommand(p.getShellType().getPrintVariableCommand("LOCALAPPDATA")); + path = FileNames.join(base, "X-Pipe"); } else { - return FileNames.join("app/bin/xpiped"); + path = "/opt/xpipe"; + } + + try (CommandProcessControl c = + p.command(p.getShellType().createFileExistsCommand(path)).start()) { + if (!c.discardAndCheckExit()) { + throw new IOException("Installation not found in " + path); + } + } + + return path; + } + + public static String getDaemonDebugScriptPath(OsType type) { + if (type.equals(OsType.WINDOWS)) { + return FileNames.join("app", "scripts", "xpiped_debug.bat"); + } else { + return FileNames.join("app", "scripts", "xpiped_debug.sh"); + } + } + + public static String getDaemonDebugAttachScriptPath(OsType type) { + if (type.equals(OsType.WINDOWS)) { + return FileNames.join("app", "scripts", "xpiped_debug_attach.bat"); + } else { + return FileNames.join("app", "scripts", "xpiped_debug_attach.sh"); + } + } + + public static String getDaemonExecutablePath(OsType type) { + if (type.equals(OsType.WINDOWS)) { + return FileNames.join("app", "xpiped.exe"); + } else { + return FileNames.join("app", "bin", "xpiped"); } } } diff --git a/extension/src/main/java/io/xpipe/extension/NamedFunction.java b/extension/src/main/java/io/xpipe/extension/NamedFunction.java deleted file mode 100644 index f49f5dea..00000000 --- a/extension/src/main/java/io/xpipe/extension/NamedFunction.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.xpipe.extension; - -import io.xpipe.beacon.exchange.NamedFunctionExchange; -import io.xpipe.extension.event.ErrorEvent; -import lombok.Getter; -import lombok.SneakyThrows; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.ServiceLoader; - -@Getter -public class NamedFunction { - - public static final List ALL = new ArrayList<>(); - - public static void init(ModuleLayer layer) { - if (ALL.size() == 0) { - ALL.addAll(ServiceLoader.load(layer, NamedFunction.class).stream() - .map(p -> p.get()) - .toList()); - } - } - - public static NamedFunction get(String id) { - return ALL.stream() - .filter(namedFunction -> namedFunction.id.equalsIgnoreCase(id)) - .findFirst() - .orElseThrow(); - } - - public static T callLocal(String id, Object... args) { - return get(id).callLocal(args); - } - - @SneakyThrows - public static T callRemote(String id, Object... args) { - var proxy = XPipeProxy.getProxy(args[0]); - var client = XPipeProxy.connect(proxy); - client.sendRequest( - NamedFunctionExchange.Request.builder().id(id).arguments(args).build()); - NamedFunctionExchange.Response response = client.receiveResponse(); - return (T) response.getReturnValue(); - } - - @SneakyThrows - public static T call(Class clazz, Object... args) { - var base = args[0]; - var proxy = XPipeProxy.getProxy(base); - if (proxy != null) { - return callRemote(clazz.getDeclaredConstructor().newInstance().getId(), args); - } else { - return callLocal(clazz.getDeclaredConstructor().newInstance().getId(), args); - } - } - - private final String id; - private final Method method; - - public NamedFunction(String id, Method method) { - this.id = id; - this.method = method; - } - - public NamedFunction(String id, Class clazz) { - this.id = id; - this.method = Arrays.stream(clazz.getDeclaredMethods()) - .filter(method1 -> method1.getName().equals(id)) - .findFirst() - .orElseThrow(); - } - - public T callLocal(Object... args) { - try { - return (T) method.invoke(null, args); - } catch (Throwable ex) { - ErrorEvent.fromThrowable(ex).handle(); - return null; - } - } -} diff --git a/extension/src/main/java/io/xpipe/extension/Proxyable.java b/extension/src/main/java/io/xpipe/extension/Proxyable.java deleted file mode 100644 index 56fba18b..00000000 --- a/extension/src/main/java/io/xpipe/extension/Proxyable.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.xpipe.extension; - -import io.xpipe.core.store.ShellStore; - -public interface Proxyable { - - ShellStore getProxy(); -} diff --git a/extension/src/main/java/io/xpipe/extension/XPipeProxy.java b/extension/src/main/java/io/xpipe/extension/XPipeProxy.java index c35a8427..66b3b54d 100644 --- a/extension/src/main/java/io/xpipe/extension/XPipeProxy.java +++ b/extension/src/main/java/io/xpipe/extension/XPipeProxy.java @@ -2,9 +2,9 @@ package io.xpipe.extension; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import io.xpipe.api.connector.XPipeConnection; -import io.xpipe.beacon.BeaconClient; +import io.xpipe.api.connector.XPipeApiConnection; import io.xpipe.beacon.exchange.ProxyReadConnectionExchange; +import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.InputStreamStore; import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.source.DataSource; @@ -21,18 +21,8 @@ import java.util.function.Function; public class XPipeProxy { - public static BeaconClient connect(ShellStore proxy) throws Exception { - var control = proxy.create().start(); - var command = control.command("xpipe beacon").start(); - return BeaconClient.connectGateway( - command, - BeaconClient.GatewayClientInformation.builder() - .version(XPipeDaemon.getInstance().getVersion()) - .build()); - } - @SneakyThrows - private static DataSource downstreamTransform(DataSource input, ShellStore proxy) { + private static DataSource downstreamTransform(DataSource input, ShellStore proxy) { var proxyNode = JacksonMapper.newMapper().valueToTree(proxy); var inputNode = JacksonMapper.newMapper().valueToTree(input); var localNode = JacksonMapper.newMapper().valueToTree(ShellStore.local()); @@ -40,8 +30,7 @@ public class XPipeProxy { return JacksonMapper.newMapper().treeToValue(inputNode, DataSource.class); } - private static JsonNode replace( - JsonNode node, Function> function) { + private static JsonNode replace(JsonNode node, Function> function) { var value = function.apply(node); if (value.isPresent()) { return value.get(); @@ -63,8 +52,10 @@ public class XPipeProxy { public static T remoteReadConnection(DataSource source, ShellStore proxy) { var downstream = downstreamTransform(source, proxy); - return (T) XPipeConnection.execute(con -> { - con.sendRequest(ProxyReadConnectionExchange.Request.builder().source(downstream).build()); + return (T) XPipeApiConnection.execute(con -> { + con.sendRequest(ProxyReadConnectionExchange.Request.builder() + .source(downstream) + .build()); con.receiveResponse(); var inputSource = DataSource.createInternalDataSource( source.determineInfo().getType(), new InputStreamStore(con.receiveBody())); @@ -72,23 +63,18 @@ public class XPipeProxy { }); } - public static ShellStore getProxy(Object base) { - return base instanceof Proxyable p ? p.getProxy() : null; - } - - public static boolean isRemote(Object base) { - return base instanceof Proxyable p && !ShellStore.isLocal(p.getProxy()); - } - public static void checkSupport(ShellStore store) throws Exception { var version = XPipeDaemon.getInstance().getVersion(); try (ShellProcessControl s = store.create().start()) { - var installationVersion = XPipeInstallation.queryInstallationVersion(s); - if (installationVersion.isEmpty()) { + var defaultInstallationExecutable = FileNames.join( + XPipeInstallation.getDefaultInstallationBasePath(s), + XPipeInstallation.getDaemonExecutablePath(s.getOsType())); + if (!s.executeBooleanSimpleCommand(s.getShellType().createFileExistsCommand(defaultInstallationExecutable))) { throw new IOException(I18n.get("noInstallationFound")); } - if (!version.equals(installationVersion.get())) { + var installationVersion = XPipeInstallation.queryInstallationVersion(s, defaultInstallationExecutable); + if (!version.equals(installationVersion)) { throw new IOException(I18n.get("versionMismatch", version, installationVersion)); } } diff --git a/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java b/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java index 22e341aa..a7fdbbee 100644 --- a/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java @@ -35,7 +35,6 @@ public class XPipeServiceProviders { SupportedApplicationProviders.loadAll(layer); PrefsProviders.init(layer); - NamedFunction.init(layer); TrackEvent.info("Finished loading extension providers"); } } diff --git a/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java index bebc9af5..7ef2727f 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java +++ b/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java @@ -1,7 +1,7 @@ package io.xpipe.extension.util; import io.xpipe.api.DataSource; -import io.xpipe.api.util.XPipeDaemonController; +import io.xpipe.beacon.BeaconDaemonController; import io.xpipe.core.store.DataStore; import io.xpipe.core.util.JacksonMapper; import io.xpipe.extension.XPipeServiceProviders; @@ -26,11 +26,11 @@ public class DaemonExtensionTest extends ExtensionTest { public static void setup() throws Exception { JacksonMapper.initModularized(ModuleLayer.boot()); XPipeServiceProviders.load(ModuleLayer.boot()); - XPipeDaemonController.start(); + BeaconDaemonController.start(); } @AfterAll public static void teardown() throws Exception { - XPipeDaemonController.stop(); + BeaconDaemonController.stop(); } } diff --git a/extension/src/main/java/module-info.java b/extension/src/main/java/module-info.java index c44cd597..cafb2428 100644 --- a/extension/src/main/java/module-info.java +++ b/extension/src/main/java/module-info.java @@ -1,3 +1,4 @@ +import io.xpipe.beacon.NamedFunction; import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.DataStoreActionProvider; import io.xpipe.extension.SupportedApplicationProvider; @@ -40,5 +41,5 @@ open module io.xpipe.extension { uses XPipeDaemon; uses io.xpipe.extension.Cache; uses io.xpipe.extension.DataSourceActionProvider; - uses io.xpipe.extension.NamedFunction; + uses NamedFunction; }