mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-23 08:00:23 +00:00
Refactor
This commit is contained in:
parent
a351b92ac7
commit
55e254a54c
27 changed files with 350 additions and 333 deletions
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.api;
|
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.api.util.QuietDialogHandler;
|
||||||
import io.xpipe.beacon.exchange.cli.StoreAddExchange;
|
import io.xpipe.beacon.exchange.cli.StoreAddExchange;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
@ -10,7 +10,7 @@ import java.util.Map;
|
||||||
public class DataStores {
|
public class DataStores {
|
||||||
|
|
||||||
public static void addNamedStore(DataStore store, String name) {
|
public static void addNamedStore(DataStore store, String name) {
|
||||||
XPipeConnection.execute(con -> {
|
XPipeApiConnection.execute(con -> {
|
||||||
var req = StoreAddExchange.Request.builder()
|
var req = StoreAddExchange.Request.builder()
|
||||||
.storeInput(store)
|
.storeInput(store)
|
||||||
.name(name)
|
.name(name)
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
package io.xpipe.api.connector;
|
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.beacon.exchange.cli.DialogExchange;
|
||||||
import io.xpipe.core.dialog.DialogReference;
|
import io.xpipe.core.dialog.DialogReference;
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public final class XPipeConnection extends BeaconConnection {
|
public final class XPipeApiConnection extends BeaconConnection {
|
||||||
|
|
||||||
private XPipeConnection() {}
|
private XPipeApiConnection() {}
|
||||||
|
|
||||||
public static XPipeConnection open() {
|
public static XPipeApiConnection open() {
|
||||||
var con = new XPipeConnection();
|
var con = new XPipeApiConnection();
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void finishDialog(DialogReference reference) {
|
public static void finishDialog(DialogReference reference) {
|
||||||
try (var con = new XPipeConnection()) {
|
try (var con = new XPipeApiConnection()) {
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
var element = reference.getStart();
|
var element = reference.getStart();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -41,7 +45,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void execute(Handler handler) {
|
public static void execute(Handler handler) {
|
||||||
try (var con = new XPipeConnection()) {
|
try (var con = new XPipeApiConnection()) {
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
handler.handle(con);
|
handler.handle(con);
|
||||||
} catch (BeaconException e) {
|
} catch (BeaconException e) {
|
||||||
|
@ -52,7 +56,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T execute(Mapper<T> mapper) {
|
public static <T> T execute(Mapper<T> mapper) {
|
||||||
try (var con = new XPipeConnection()) {
|
try (var con = new XPipeApiConnection()) {
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
return mapper.handle(con);
|
return mapper.handle(con);
|
||||||
} catch (BeaconException e) {
|
} catch (BeaconException e) {
|
||||||
|
@ -73,7 +77,10 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
} catch (InterruptedException ignored) {
|
} 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()) {
|
if (s.isPresent()) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -114,17 +121,18 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build());
|
beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder()
|
||||||
|
.version("?")
|
||||||
|
.language("Java")
|
||||||
|
.build());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
|
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() throws Exception {
|
private void start() throws Exception {
|
||||||
if (BeaconServer.tryStart() == null) {
|
var installation = XPipeInstallation.getDefaultInstallationBasePath();
|
||||||
throw new UnsupportedOperationException("Unable to determine xpipe daemon launch command");
|
BeaconServer.start(installation);
|
||||||
}
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
|
@ -2,7 +2,7 @@ package io.xpipe.api.impl;
|
||||||
|
|
||||||
import io.xpipe.api.DataSource;
|
import io.xpipe.api.DataSource;
|
||||||
import io.xpipe.api.DataSourceConfig;
|
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.beacon.exchange.*;
|
||||||
import io.xpipe.core.source.DataSourceId;
|
import io.xpipe.core.source.DataSourceId;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
import io.xpipe.core.source.DataSourceReference;
|
||||||
|
@ -25,7 +25,7 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource get(DataSourceReference ds) {
|
public static DataSource get(DataSourceReference ds) {
|
||||||
return XPipeConnection.execute(con -> {
|
return XPipeApiConnection.execute(con -> {
|
||||||
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
||||||
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
||||||
var config = new DataSourceConfig(res.getProvider(), res.getConfig());
|
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) {
|
public static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource<?> source) {
|
||||||
var startReq =
|
var startReq =
|
||||||
AddSourceExchange.Request.builder().source(source).target(id).build();
|
AddSourceExchange.Request.builder().source(source).target(id).build();
|
||||||
var returnedId = XPipeConnection.execute(con -> {
|
var returnedId = XPipeApiConnection.execute(con -> {
|
||||||
AddSourceExchange.Response r = con.performSimpleExchange(startReq);
|
AddSourceExchange.Response r = con.performSimpleExchange(startReq);
|
||||||
return r.getId();
|
return r.getId();
|
||||||
});
|
});
|
||||||
|
@ -64,7 +64,7 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
|
|
||||||
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
||||||
if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) {
|
if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) {
|
||||||
var res = XPipeConnection.execute(con -> {
|
var res = XPipeApiConnection.execute(con -> {
|
||||||
var req = StoreStreamExchange.Request.builder().build();
|
var req = StoreStreamExchange.Request.builder().build();
|
||||||
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> {
|
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> {
|
||||||
try (InputStream inputStream = s.openInput()) {
|
try (InputStream inputStream = s.openInput()) {
|
||||||
|
@ -83,20 +83,20 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
.target(id)
|
.target(id)
|
||||||
.configureAll(false)
|
.configureAll(false)
|
||||||
.build();
|
.build();
|
||||||
var startRes = XPipeConnection.execute(con -> {
|
var startRes = XPipeApiConnection.execute(con -> {
|
||||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
|
||||||
var configInstance = startRes.getConfig();
|
var configInstance = startRes.getConfig();
|
||||||
XPipeConnection.finishDialog(configInstance);
|
XPipeApiConnection.finishDialog(configInstance);
|
||||||
|
|
||||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||||
return get(ref);
|
return get(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
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();
|
var req = StoreStreamExchange.Request.builder().build();
|
||||||
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out));
|
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out));
|
||||||
return r;
|
return r;
|
||||||
|
@ -110,13 +110,13 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
.target(id)
|
.target(id)
|
||||||
.configureAll(false)
|
.configureAll(false)
|
||||||
.build();
|
.build();
|
||||||
var startRes = XPipeConnection.execute(con -> {
|
var startRes = XPipeApiConnection.execute(con -> {
|
||||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
|
||||||
var configInstance = startRes.getConfig();
|
var configInstance = startRes.getConfig();
|
||||||
XPipeConnection.finishDialog(configInstance);
|
XPipeApiConnection.finishDialog(configInstance);
|
||||||
|
|
||||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||||
return get(ref);
|
return get(ref);
|
||||||
|
@ -124,7 +124,7 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forwardTo(DataSource target) {
|
public void forwardTo(DataSource target) {
|
||||||
XPipeConnection.execute(con -> {
|
XPipeApiConnection.execute(con -> {
|
||||||
var req = ForwardExchange.Request.builder()
|
var req = ForwardExchange.Request.builder()
|
||||||
.source(DataSourceReference.id(sourceId))
|
.source(DataSourceReference.id(sourceId))
|
||||||
.target(DataSourceReference.id(target.getId()))
|
.target(DataSourceReference.id(target.getId()))
|
||||||
|
@ -135,7 +135,7 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void appendTo(DataSource target) {
|
public void appendTo(DataSource target) {
|
||||||
XPipeConnection.execute(con -> {
|
XPipeApiConnection.execute(con -> {
|
||||||
var req = ForwardExchange.Request.builder()
|
var req = ForwardExchange.Request.builder()
|
||||||
.source(DataSourceReference.id(sourceId))
|
.source(DataSourceReference.id(sourceId))
|
||||||
.target(DataSourceReference.id(target.getId()))
|
.target(DataSourceReference.id(target.getId()))
|
||||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.api.impl;
|
||||||
import io.xpipe.api.DataSource;
|
import io.xpipe.api.DataSource;
|
||||||
import io.xpipe.api.DataTable;
|
import io.xpipe.api.DataTable;
|
||||||
import io.xpipe.api.DataTableAccumulator;
|
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.api.util.TypeDescriptor;
|
||||||
import io.xpipe.beacon.BeaconException;
|
import io.xpipe.beacon.BeaconException;
|
||||||
import io.xpipe.beacon.exchange.ReadExchange;
|
import io.xpipe.beacon.exchange.ReadExchange;
|
||||||
|
@ -22,7 +22,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
||||||
|
|
||||||
private final XPipeConnection connection;
|
private final XPipeApiConnection connection;
|
||||||
private final TupleType type;
|
private final TupleType type;
|
||||||
private int rows;
|
private int rows;
|
||||||
private TupleType writtenDescriptor;
|
private TupleType writtenDescriptor;
|
||||||
|
@ -30,7 +30,7 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
||||||
|
|
||||||
public DataTableAccumulatorImpl(TupleType type) {
|
public DataTableAccumulatorImpl(TupleType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
connection = XPipeConnection.open();
|
connection = XPipeApiConnection.open();
|
||||||
connection.sendRequest(StoreStreamExchange.Request.builder().build());
|
connection.sendRequest(StoreStreamExchange.Request.builder().build());
|
||||||
bodyOutput = connection.sendBody();
|
bodyOutput = connection.sendBody();
|
||||||
}
|
}
|
||||||
|
@ -52,12 +52,12 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
||||||
.provider("xpbt")
|
.provider("xpbt")
|
||||||
.configureAll(false)
|
.configureAll(false)
|
||||||
.build();
|
.build();
|
||||||
ReadExchange.Response response = XPipeConnection.execute(con -> {
|
ReadExchange.Response response = XPipeApiConnection.execute(con -> {
|
||||||
return con.performSimpleExchange(req);
|
return con.performSimpleExchange(req);
|
||||||
});
|
});
|
||||||
|
|
||||||
var configInstance = response.getConfig();
|
var configInstance = response.getConfig();
|
||||||
XPipeConnection.finishDialog(configInstance);
|
XPipeApiConnection.finishDialog(configInstance);
|
||||||
|
|
||||||
return DataSource.get(DataSourceReference.id(id)).asTable();
|
return DataSource.get(DataSourceReference.id(id)).asTable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.api.impl;
|
||||||
|
|
||||||
import io.xpipe.api.DataSourceConfig;
|
import io.xpipe.api.DataSourceConfig;
|
||||||
import io.xpipe.api.DataTable;
|
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.BeaconConnection;
|
||||||
import io.xpipe.beacon.BeaconException;
|
import io.xpipe.beacon.BeaconException;
|
||||||
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
||||||
|
@ -55,7 +55,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||||
@Override
|
@Override
|
||||||
public ArrayNode read(int maxRows) {
|
public ArrayNode read(int maxRows) {
|
||||||
List<DataStructureNode> nodes = new ArrayList<>();
|
List<DataStructureNode> nodes = new ArrayList<>();
|
||||||
XPipeConnection.execute(con -> {
|
XPipeApiConnection.execute(con -> {
|
||||||
var req = QueryTableDataExchange.Request.builder()
|
var req = QueryTableDataExchange.Request.builder()
|
||||||
.ref(DataSourceReference.id(getId()))
|
.ref(DataSourceReference.id(getId()))
|
||||||
.maxRows(maxRows)
|
.maxRows(maxRows)
|
||||||
|
@ -83,7 +83,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||||
private TupleNode node;
|
private TupleNode node;
|
||||||
|
|
||||||
{
|
{
|
||||||
connection = XPipeConnection.open();
|
connection = XPipeApiConnection.open();
|
||||||
var req = QueryTableDataExchange.Request.builder()
|
var req = QueryTableDataExchange.Request.builder()
|
||||||
.ref(DataSourceReference.id(getId()))
|
.ref(DataSourceReference.id(getId()))
|
||||||
.maxRows(Integer.MAX_VALUE)
|
.maxRows(Integer.MAX_VALUE)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.api.impl;
|
||||||
|
|
||||||
import io.xpipe.api.DataSourceConfig;
|
import io.xpipe.api.DataSourceConfig;
|
||||||
import io.xpipe.api.DataText;
|
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.BeaconConnection;
|
||||||
import io.xpipe.beacon.BeaconException;
|
import io.xpipe.beacon.BeaconException;
|
||||||
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
||||||
|
@ -62,7 +62,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||||
private String nextValue;
|
private String nextValue;
|
||||||
|
|
||||||
{
|
{
|
||||||
connection = XPipeConnection.open();
|
connection = XPipeApiConnection.open();
|
||||||
var req = QueryTextDataExchange.Request.builder()
|
var req = QueryTextDataExchange.Request.builder()
|
||||||
.ref(DataSourceReference.id(getId()))
|
.ref(DataSourceReference.id(getId()))
|
||||||
.maxLines(-1)
|
.maxLines(-1)
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.api.test;
|
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.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@ public class ApiTest {
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup() throws Exception {
|
public static void setup() throws Exception {
|
||||||
XPipeDaemonController.start();
|
BeaconDaemonController.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
public static void teardown() throws Exception {
|
public static void teardown() throws Exception {
|
||||||
XPipeDaemonController.stop();
|
BeaconDaemonController.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import io.xpipe.beacon.exchange.MessageExchanges;
|
||||||
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
||||||
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
||||||
import io.xpipe.core.process.CommandProcessControl;
|
import io.xpipe.core.process.CommandProcessControl;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -47,12 +48,11 @@ public class BeaconClient implements AutoCloseable {
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
public static class CliClientInformation extends ClientInformation {
|
public static class CliClientInformation extends ClientInformation {
|
||||||
|
|
||||||
String version;
|
|
||||||
int consoleWidth;
|
int consoleWidth;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDisplayString() {
|
public String toDisplayString() {
|
||||||
return "X-Pipe CLI " + version;
|
return "X-Pipe CLI";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,16 @@ public class BeaconClient implements AutoCloseable {
|
||||||
return client;
|
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());
|
var client = new BeaconClient(() -> {}, control.getStdout(), control.getStdin());
|
||||||
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
||||||
return client;
|
return client;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
package io.xpipe.beacon;
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
import io.xpipe.beacon.exchange.StopExchange;
|
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 lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains basic functionality to start, communicate, and stop a remote beacon server.
|
* Contains basic functionality to start, communicate, and stop a remote beacon server.
|
||||||
|
@ -16,16 +17,8 @@ import java.util.Optional;
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class BeaconServer {
|
public class BeaconServer {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
if (tryStartCustom() == null) {
|
|
||||||
if (tryStart() == null) {
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRunning() {
|
public static boolean isRunning() {
|
||||||
try (var socket = BeaconClient.connect(null)) {
|
try (var ignored = BeaconClient.connect(null)) {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -44,18 +37,14 @@ public class BeaconServer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Process tryStart() throws Exception {
|
public static Process start(String installationBase) throws Exception {
|
||||||
var daemonExecutable = getDaemonExecutable();
|
var daemonExecutable = getDaemonExecutable(installationBase);
|
||||||
if (daemonExecutable.isPresent()) {
|
// Tell daemon that we launched from an external tool
|
||||||
var command = "\"" + daemonExecutable.get() + "\" --external "
|
var command = "\"" + daemonExecutable + "\" --external "
|
||||||
+ (BeaconConfig.getDaemonArguments() != null ? BeaconConfig.getDaemonArguments() : "");
|
+ (BeaconConfig.getDaemonArguments() != null ? BeaconConfig.getDaemonArguments() : "");
|
||||||
// Tell daemon that we launched from an external tool
|
Process process = Runtime.getRuntime().exec(command);
|
||||||
Process process = Runtime.getRuntime().exec(command);
|
printDaemonOutput(process, command);
|
||||||
printDaemonOutput(process, command);
|
return process;
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printDaemonOutput(Process proc, String command) {
|
private static void printDaemonOutput(Process proc, String command) {
|
||||||
|
@ -111,65 +100,21 @@ public class BeaconServer {
|
||||||
return res.isSuccess();
|
return res.isSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<Path> getDaemonBasePath(OsType type) {
|
public static Path getDaemonExecutable(String installationBase) throws Exception {
|
||||||
Path base = null;
|
try (ShellProcessControl pc = new LocalStore().create().start()) {
|
||||||
// Prepare for invalid XPIPE_HOME path value
|
var debug = BeaconConfig.launchDaemonInDebugMode();
|
||||||
try {
|
if (!debug) {
|
||||||
var environmentVariable = System.getenv("XPIPE_HOME");
|
return Path.of(
|
||||||
base = environmentVariable != null ? Path.of(environmentVariable) : null;
|
FileNames.join(installationBase, XPipeInstallation.getDaemonExecutablePath(pc.getOsType())));
|
||||||
} catch (Exception ex) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base == null) {
|
|
||||||
if (type.equals(OsType.WINDOWS)) {
|
|
||||||
base = Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe");
|
|
||||||
} else {
|
} 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<Path> 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
beacon/src/main/java/io/xpipe/beacon/NamedFunction.java
Normal file
37
beacon/src/main/java/io/xpipe/beacon/NamedFunction.java
Normal file
|
@ -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<T> {
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package io.xpipe.beacon.exchange;
|
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.RequestMessage;
|
||||||
import io.xpipe.beacon.ResponseMessage;
|
import io.xpipe.beacon.ResponseMessage;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
@ -19,11 +18,7 @@ public class NamedFunctionExchange implements MessageExchange {
|
||||||
@Builder
|
@Builder
|
||||||
@Value
|
@Value
|
||||||
public static class Request implements RequestMessage {
|
public static class Request implements RequestMessage {
|
||||||
@NonNull
|
NamedFunction<?> function;
|
||||||
String id;
|
|
||||||
|
|
||||||
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="type")
|
|
||||||
@NonNull Object[] arguments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
|
|
|
@ -25,6 +25,7 @@ module io.xpipe.beacon {
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
|
|
||||||
uses MessageExchange;
|
uses MessageExchange;
|
||||||
|
uses io.xpipe.beacon.NamedFunction;
|
||||||
|
|
||||||
provides Module with BeaconJacksonModule;
|
provides Module with BeaconJacksonModule;
|
||||||
provides io.xpipe.beacon.exchange.MessageExchange with
|
provides io.xpipe.beacon.exchange.MessageExchange with
|
||||||
|
|
|
@ -18,6 +18,14 @@ public class FileNames {
|
||||||
return normalize(joined);
|
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) {
|
public static String normalize(String file) {
|
||||||
var backslash = file.contains("\\");
|
var backslash = file.contains("\\");
|
||||||
return backslash ? toWindows(file) : toUnix(file);
|
return backslash ? toWindows(file) : toUnix(file);
|
||||||
|
|
|
@ -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 {
|
default String executeSimpleCommand(ShellType type, String command) throws Exception {
|
||||||
return executeSimpleCommand(type.switchTo(command));
|
return executeSimpleCommand(type.switchTo(command));
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ public interface ShellType {
|
||||||
|
|
||||||
String createFileWriteCommand(String file);
|
String createFileWriteCommand(String file);
|
||||||
|
|
||||||
List<String> createFileExistsCommand(String file);
|
String createFileExistsCommand(String file);
|
||||||
|
|
||||||
Charset determineCharset(ShellProcessControl control) throws Exception;
|
Charset determineCharset(ShellProcessControl control) throws Exception;
|
||||||
|
|
||||||
|
|
|
@ -132,8 +132,8 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> createFileExistsCommand(String file) {
|
public String createFileExistsCommand(String file) {
|
||||||
return List.of("dir", "/a", file);
|
return String.format("dir /a \"%s\"", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -263,8 +263,8 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> createFileExistsCommand(String file) {
|
public String createFileExistsCommand(String file) {
|
||||||
return List.of("cmd", "/c", "dir", "/a", file);
|
return String.format("cmd /c dir /a \"%s\"", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -386,8 +386,8 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> createFileExistsCommand(String file) {
|
public String createFileExistsCommand(String file) {
|
||||||
return List.of("(", "test", "-f", file, "||", "test", "-d", file, ")");
|
return String.format("test -f \"%s\" || test -d \"%s\"", file, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -37,7 +37,7 @@ public interface MachineStore extends FileSystemStore, ShellStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public default boolean exists(String file) throws Exception {
|
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()) {
|
.start()) {
|
||||||
return pc.discardAndCheckExit();
|
return pc.discardAndCheckExit();
|
||||||
}
|
}
|
||||||
|
|
21
core/src/main/java/io/xpipe/core/util/Proxyable.java
Normal file
21
core/src/main/java/io/xpipe/core/util/Proxyable.java
Normal file
|
@ -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();
|
||||||
|
}
|
|
@ -1,43 +1,56 @@
|
||||||
package io.xpipe.core.util;
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
import io.xpipe.core.impl.FileNames;
|
import io.xpipe.core.impl.FileNames;
|
||||||
|
import io.xpipe.core.impl.LocalStore;
|
||||||
import io.xpipe.core.process.CommandProcessControl;
|
import io.xpipe.core.process.CommandProcessControl;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class XPipeInstallation {
|
public class XPipeInstallation {
|
||||||
|
|
||||||
public static Optional<String> queryInstallationVersion(ShellProcessControl p) throws Exception {
|
public static String getInstallationBasePathForCLI(ShellProcessControl p, String cliExecutable) throws Exception {
|
||||||
var executable = getInstallationExecutable(p);
|
var defaultInstallation = getDefaultInstallationBasePath(p);
|
||||||
if (executable.isEmpty()) {
|
if (p.getOsType().equals(OsType.LINUX) && cliExecutable.equals("/usr/bin/xpipe")) {
|
||||||
return Optional.empty();
|
return defaultInstallation;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (CommandProcessControl c = p.command(executable.get() + " version").start()) {
|
if (FileNames.startsWith(cliExecutable, defaultInstallation)) {
|
||||||
return Optional.ofNullable(c.readOrThrow());
|
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 {
|
public static boolean containsCompatibleDefaultInstallation(ShellProcessControl p, String version) throws Exception {
|
||||||
var executable = getInstallationExecutable(p);
|
var defaultBase = getDefaultInstallationBasePath(p);
|
||||||
|
var executable = getInstallationExecutable(p, defaultBase);
|
||||||
if (executable.isEmpty()) {
|
if (executable.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (CommandProcessControl c = p.command(executable.get() + " version").start()) {
|
try (CommandProcessControl c = p.command(executable + " version").start()) {
|
||||||
return c.readOrThrow().equals(version);
|
return c.readOrThrow().equals(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<String> getInstallationExecutable(ShellProcessControl p) throws Exception {
|
public static String getInstallationExecutable(ShellProcessControl p, String installation) throws Exception {
|
||||||
var installation = getDefaultInstallationBasePath(p);
|
var executable = getDaemonExecutablePath(p.getOsType());
|
||||||
var executable = getDaemonExecutableInInstallationDirectory(p.getOsType());
|
|
||||||
var file = FileNames.join(installation, executable);
|
var file = FileNames.join(installation, executable);
|
||||||
try (CommandProcessControl c =
|
try (CommandProcessControl c =
|
||||||
p.command(p.getShellType().createFileExistsCommand(file)).start()) {
|
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 {
|
public static String getDefaultInstallationBasePath() throws Exception {
|
||||||
if (p.getOsType().equals(OsType.WINDOWS)) {
|
try (ShellProcessControl pc = new LocalStore().create().start()) {
|
||||||
var base = p.executeSimpleCommand(p.getShellType().getPrintVariableCommand("LOCALAPPDATA"));
|
return getDefaultInstallationBasePath(pc);
|
||||||
return FileNames.join(base, "X-Pipe");
|
|
||||||
} else {
|
|
||||||
return "/opt/xpipe";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getDaemonExecutableInInstallationDirectory(OsType type) {
|
public static String getDefaultInstallationBasePath(ShellProcessControl p) throws Exception {
|
||||||
if (type.equals(OsType.WINDOWS)) {
|
var customHome = p.executeSimpleCommand(p.getShellType().getPrintVariableCommand("XPIPE_HOME"));
|
||||||
return FileNames.join("app", "runtime", "bin", "xpiped.bat");
|
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 {
|
} 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<NamedFunction> 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> T callLocal(String id, Object... args) {
|
|
||||||
return get(id).callLocal(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static <T> 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> T call(Class<? extends NamedFunction> 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> T callLocal(Object... args) {
|
|
||||||
try {
|
|
||||||
return (T) method.invoke(null, args);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package io.xpipe.extension;
|
|
||||||
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
public interface Proxyable {
|
|
||||||
|
|
||||||
ShellStore getProxy();
|
|
||||||
}
|
|
|
@ -2,9 +2,9 @@ package io.xpipe.extension;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import io.xpipe.api.connector.XPipeConnection;
|
import io.xpipe.api.connector.XPipeApiConnection;
|
||||||
import io.xpipe.beacon.BeaconClient;
|
|
||||||
import io.xpipe.beacon.exchange.ProxyReadConnectionExchange;
|
import io.xpipe.beacon.exchange.ProxyReadConnectionExchange;
|
||||||
|
import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.impl.InputStreamStore;
|
import io.xpipe.core.impl.InputStreamStore;
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
|
@ -21,18 +21,8 @@ import java.util.function.Function;
|
||||||
|
|
||||||
public class XPipeProxy {
|
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
|
@SneakyThrows
|
||||||
private static DataSource<?> downstreamTransform(DataSource<?> input, ShellStore proxy) {
|
private static DataSource<?> downstreamTransform(DataSource<?> input, ShellStore proxy) {
|
||||||
var proxyNode = JacksonMapper.newMapper().valueToTree(proxy);
|
var proxyNode = JacksonMapper.newMapper().valueToTree(proxy);
|
||||||
var inputNode = JacksonMapper.newMapper().valueToTree(input);
|
var inputNode = JacksonMapper.newMapper().valueToTree(input);
|
||||||
var localNode = JacksonMapper.newMapper().valueToTree(ShellStore.local());
|
var localNode = JacksonMapper.newMapper().valueToTree(ShellStore.local());
|
||||||
|
@ -40,8 +30,7 @@ public class XPipeProxy {
|
||||||
return JacksonMapper.newMapper().treeToValue(inputNode, DataSource.class);
|
return JacksonMapper.newMapper().treeToValue(inputNode, DataSource.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonNode replace(
|
private static JsonNode replace(JsonNode node, Function<JsonNode, Optional<JsonNode>> function) {
|
||||||
JsonNode node, Function<JsonNode, Optional<JsonNode>> function) {
|
|
||||||
var value = function.apply(node);
|
var value = function.apply(node);
|
||||||
if (value.isPresent()) {
|
if (value.isPresent()) {
|
||||||
return value.get();
|
return value.get();
|
||||||
|
@ -63,8 +52,10 @@ public class XPipeProxy {
|
||||||
|
|
||||||
public static <T extends DataSourceReadConnection> T remoteReadConnection(DataSource<?> source, ShellStore proxy) {
|
public static <T extends DataSourceReadConnection> T remoteReadConnection(DataSource<?> source, ShellStore proxy) {
|
||||||
var downstream = downstreamTransform(source, proxy);
|
var downstream = downstreamTransform(source, proxy);
|
||||||
return (T) XPipeConnection.execute(con -> {
|
return (T) XPipeApiConnection.execute(con -> {
|
||||||
con.sendRequest(ProxyReadConnectionExchange.Request.builder().source(downstream).build());
|
con.sendRequest(ProxyReadConnectionExchange.Request.builder()
|
||||||
|
.source(downstream)
|
||||||
|
.build());
|
||||||
con.receiveResponse();
|
con.receiveResponse();
|
||||||
var inputSource = DataSource.createInternalDataSource(
|
var inputSource = DataSource.createInternalDataSource(
|
||||||
source.determineInfo().getType(), new InputStreamStore(con.receiveBody()));
|
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 {
|
public static void checkSupport(ShellStore store) throws Exception {
|
||||||
var version = XPipeDaemon.getInstance().getVersion();
|
var version = XPipeDaemon.getInstance().getVersion();
|
||||||
try (ShellProcessControl s = store.create().start()) {
|
try (ShellProcessControl s = store.create().start()) {
|
||||||
var installationVersion = XPipeInstallation.queryInstallationVersion(s);
|
var defaultInstallationExecutable = FileNames.join(
|
||||||
if (installationVersion.isEmpty()) {
|
XPipeInstallation.getDefaultInstallationBasePath(s),
|
||||||
|
XPipeInstallation.getDaemonExecutablePath(s.getOsType()));
|
||||||
|
if (!s.executeBooleanSimpleCommand(s.getShellType().createFileExistsCommand(defaultInstallationExecutable))) {
|
||||||
throw new IOException(I18n.get("noInstallationFound"));
|
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));
|
throw new IOException(I18n.get("versionMismatch", version, installationVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ public class XPipeServiceProviders {
|
||||||
|
|
||||||
SupportedApplicationProviders.loadAll(layer);
|
SupportedApplicationProviders.loadAll(layer);
|
||||||
PrefsProviders.init(layer);
|
PrefsProviders.init(layer);
|
||||||
NamedFunction.init(layer);
|
|
||||||
TrackEvent.info("Finished loading extension providers");
|
TrackEvent.info("Finished loading extension providers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.xpipe.extension.util;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.api.DataSource;
|
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.store.DataStore;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.extension.XPipeServiceProviders;
|
import io.xpipe.extension.XPipeServiceProviders;
|
||||||
|
@ -26,11 +26,11 @@ public class DaemonExtensionTest extends ExtensionTest {
|
||||||
public static void setup() throws Exception {
|
public static void setup() throws Exception {
|
||||||
JacksonMapper.initModularized(ModuleLayer.boot());
|
JacksonMapper.initModularized(ModuleLayer.boot());
|
||||||
XPipeServiceProviders.load(ModuleLayer.boot());
|
XPipeServiceProviders.load(ModuleLayer.boot());
|
||||||
XPipeDaemonController.start();
|
BeaconDaemonController.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
public static void teardown() throws Exception {
|
public static void teardown() throws Exception {
|
||||||
XPipeDaemonController.stop();
|
BeaconDaemonController.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import io.xpipe.beacon.NamedFunction;
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.DataStoreActionProvider;
|
import io.xpipe.extension.DataStoreActionProvider;
|
||||||
import io.xpipe.extension.SupportedApplicationProvider;
|
import io.xpipe.extension.SupportedApplicationProvider;
|
||||||
|
@ -40,5 +41,5 @@ open module io.xpipe.extension {
|
||||||
uses XPipeDaemon;
|
uses XPipeDaemon;
|
||||||
uses io.xpipe.extension.Cache;
|
uses io.xpipe.extension.Cache;
|
||||||
uses io.xpipe.extension.DataSourceActionProvider;
|
uses io.xpipe.extension.DataSourceActionProvider;
|
||||||
uses io.xpipe.extension.NamedFunction;
|
uses NamedFunction;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue