Merge branch 'api' into release-10.0

This commit is contained in:
crschnick 2024-06-02 14:09:29 +00:00
parent f707a0b1f5
commit 56e30ce222
128 changed files with 1142 additions and 3239 deletions

View file

@ -56,9 +56,7 @@ dependencies {
api 'io.sentry:sentry:7.8.0' api 'io.sentry:sentry:7.8.0'
api 'commons-io:commons-io:2.16.1' api 'commons-io:commons-io:2.16.1'
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.1" api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.1"
api group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.17.1"
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.17.1" api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.17.1"
api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.17.1"
api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0" api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0"
api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0" api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0"
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0" api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
@ -96,9 +94,6 @@ run {
systemProperty 'io.xpipe.app.logLevel', "trace" systemProperty 'io.xpipe.app.logLevel', "trace"
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
systemProperty 'io.xpipe.app.staging', isStage systemProperty 'io.xpipe.app.staging', isStage
// systemProperty "io.xpipe.beacon.port", "21724"
// systemProperty "io.xpipe.beacon.printMessages", "true"
// systemProperty 'io.xpipe.app.debugPlatform', "true"
// Apply passed xpipe properties // Apply passed xpipe properties
for (final def e in System.getProperties().entrySet()) { for (final def e in System.getProperties().entrySet()) {

View file

@ -0,0 +1,104 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpServer;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import lombok.Getter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
public class AppBeaconServer {
private static AppBeaconServer INSTANCE;
@Getter
private final int port;
@Getter
private final boolean propertyPort;
private boolean running;
private HttpServer server;
@Getter
private final Set<BeaconSession> sessions = new HashSet<>();
static {
int port;
boolean propertyPort;
if (System.getProperty(BeaconConfig.BEACON_PORT_PROP) != null) {
port = BeaconConfig.getUsedPort();
propertyPort = true;
} else {
port = AppPrefs.get().httpServerPort().getValue();
propertyPort = false;
}
INSTANCE = new AppBeaconServer(port, propertyPort);
}
private AppBeaconServer(int port, boolean propertyPort) {
this.port = port;
this.propertyPort = propertyPort;
}
public static void init() {
try {
INSTANCE.start();
TrackEvent.withInfo("Started http server")
.tag("port", INSTANCE.getPort())
.build()
.handle();
} catch (Exception ex) {
// Not terminal!
// We can still continue without the running server
ErrorEvent.fromThrowable(ex)
.description("Unable to start local http server on port " + INSTANCE.getPort())
.build()
.handle();
}
}
public static void reset() {
if (INSTANCE != null) {
INSTANCE.stop();
INSTANCE = null;
}
}
public void addSession(BeaconSession session) {
this.sessions.add(session);
}
public static AppBeaconServer get() {
return INSTANCE;
}
private void stop() {
if (!running) {
return;
}
running = false;
server.stop(1);
}
private void start() throws IOException {
server = HttpServer.create(new InetSocketAddress("localhost", port), 10);
BeaconInterface.getAll().forEach(beaconInterface -> {
server.createContext(beaconInterface.getPath(), new BeaconRequestHandler<>(beaconInterface));
});
server.setExecutor(Executors.newSingleThreadExecutor(r -> {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setName("http handler");
t.setUncaughtExceptionHandler((t1, e) -> {
ErrorEvent.fromThrowable(e).handle();
});
return t;
}));
server.start();
running = true;
}
}

View file

@ -0,0 +1,122 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.beacon.*;
import io.xpipe.core.util.JacksonMapper;
import lombok.SneakyThrows;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class BeaconRequestHandler<T> implements HttpHandler {
private final BeaconInterface<T> beaconInterface;
public BeaconRequestHandler(BeaconInterface<T> beaconInterface) {this.beaconInterface = beaconInterface;}
@Override
public void handle(HttpExchange exchange) throws IOException {
if (beaconInterface.requiresAuthentication()) {
var auth = exchange.getRequestHeaders().getFirst("Authorization");
if (auth == null) {
writeError(exchange, new BeaconClientErrorResponse("Missing Authorization header"), 401);
return;
}
var token = auth.replace("Bearer ", "");
var session = AppBeaconServer.get().getSessions().stream().filter(s -> s.getToken().equals(token)).findFirst().orElse(null);
if (session == null) {
writeError(exchange, new BeaconClientErrorResponse("Unknown token"), 403);
return;
}
}
handleAuthenticatedRequest(exchange);
}
private void handleAuthenticatedRequest(HttpExchange exchange) {
T object;
Object response;
try {
try (InputStream is = exchange.getRequestBody()) {
var tree = JacksonMapper.getDefault().readTree(is);
TrackEvent.trace("Parsed raw request:\n" + tree.toPrettyString());
var emptyRequestClass = tree.isEmpty() && beaconInterface.getRequestClass().getDeclaredFields().length == 0;
object = emptyRequestClass ? createDefaultRequest(beaconInterface) : JacksonMapper.getDefault().treeToValue(tree, beaconInterface.getRequestClass());
TrackEvent.trace("Parsed request object:\n" + object);
}
response = beaconInterface.handle(exchange, object);
} catch (BeaconClientException clientException) {
ErrorEvent.fromThrowable(clientException).omit().expected().handle();
writeError(exchange, new BeaconClientErrorResponse(clientException.getMessage()), 400);
return;
} catch (BeaconServerException serverException) {
var cause = serverException.getCause() != null ? serverException.getCause() : serverException;
ErrorEvent.fromThrowable(cause).handle();
writeError(exchange, new BeaconServerErrorResponse(cause), 500);
return;
} catch (IOException ex) {
// Handle serialization errors as normal exceptions and other IO exceptions as assuming that the connection is broken
if (!ex.getClass().getName().contains("jackson")) {
ErrorEvent.fromThrowable(ex).omit().expected().handle();
} else {
ErrorEvent.fromThrowable(ex).omit().expected().handle();
writeError(exchange, new BeaconClientErrorResponse(ex.getMessage()), 400);
}
return;
} catch (Throwable other) {
ErrorEvent.fromThrowable(other).handle();
writeError(exchange, new BeaconServerErrorResponse(other), 500);
return;
}
try {
if (response != null) {
TrackEvent.trace("Sending response:\n" + object);
var tree = JacksonMapper.getDefault().valueToTree(response);
TrackEvent.trace("Sending raw response:\n" + tree.toPrettyString());
var bytes = tree.toPrettyString().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} else {
exchange.sendResponseHeaders(200, -1);
}
} catch (IOException ioException) {
ErrorEvent.fromThrowable(ioException).omit().expected().handle();
} catch (Throwable other) {
ErrorEvent.fromThrowable(other).handle();
writeError(exchange, new BeaconServerErrorResponse(other), 500);
return;
}
}
private void writeError(HttpExchange exchange, Object errorMessage, int code) {
try {
var bytes = JacksonMapper.getDefault().writeValueAsString(errorMessage).getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(code, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} catch (IOException ex) {
ErrorEvent.fromThrowable(ex).omit().expected().handle();
}
}
@SneakyThrows
@SuppressWarnings("unchecked")
private <REQ> REQ createDefaultRequest(BeaconInterface<?> beaconInterface) {
var c = beaconInterface.getRequestClass().getDeclaredMethod("builder");
c.setAccessible(true);
var b = c.invoke(null);
var m = b.getClass().getDeclaredMethod("build");
m.setAccessible(true);
return (REQ) beaconInterface.getRequestClass().cast(m.invoke(b));
}
}

View file

@ -0,0 +1,11 @@
package io.xpipe.app.beacon;
import io.xpipe.beacon.BeaconClientInformation;
import lombok.Value;
@Value
public class BeaconSession {
BeaconClientInformation clientInformation;
String token;
}

View file

@ -1,14 +1,17 @@
package io.xpipe.app.exchange; package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.SecretManager; import io.xpipe.app.util.SecretManager;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.exchange.AskpassExchange; import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.AskpassExchange;
public class AskpassExchangeImpl extends AskpassExchange import java.io.IOException;
implements MessageExchangeImpl<AskpassExchange.Request, AskpassExchange.Response> {
public class AskpassExchangeImpl extends AskpassExchange {
@Override @Override
public Response handleRequest(BeaconHandler handler, Request msg) { public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var found = msg.getSecretId() != null var found = msg.getSecretId() != null
? SecretManager.getProgress(msg.getRequest(), msg.getSecretId()) ? SecretManager.getProgress(msg.getRequest(), msg.getSecretId())
: SecretManager.getProgress(msg.getRequest()); : SecretManager.getProgress(msg.getRequest());

View file

@ -0,0 +1,20 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.FocusExchange;
import java.io.IOException;
public class FocusExchangeImpl extends FocusExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
OperationMode.switchUp(OperationMode.map(msg.getMode()));
return Response.builder().build();
}
}

View file

@ -0,0 +1,22 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.beacon.BeaconSession;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.HandshakeExchange;
import java.io.IOException;
import java.util.UUID;
public class HandshakeExchangeImpl extends HandshakeExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
var session = new BeaconSession(body.getClient(), UUID.randomUUID().toString());
AppBeaconServer.get().addSession(session);
return Response.builder().token(session.getToken()).build();
}
}

View file

@ -0,0 +1,36 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ModeExchange;
import java.io.IOException;
public class ModeExchangeImpl extends ModeExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
// Wait for startup
while (OperationMode.get() == null) {
ThreadHelper.sleep(100);
}
var mode = OperationMode.map(msg.getMode());
if (!mode.isSupported()) {
throw new BeaconClientException("Unsupported mode: " + msg.getMode().getDisplayName() + ". Supported: "
+ String.join(
", ",
OperationMode.getAll().stream()
.filter(OperationMode::isSupported)
.map(OperationMode::getId)
.toList()));
}
OperationMode.switchToSyncIfPossible(mode);
return ModeExchange.Response.builder()
.usedMode(OperationMode.map(OperationMode.get()))
.build();
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.launcher.LauncherInput;
import io.xpipe.app.util.PlatformState;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.OpenExchange;
import java.io.IOException;
public class OpenExchangeImpl extends OpenExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
if (msg.getArguments().isEmpty()) {
if (!OperationMode.switchToSyncIfPossible(OperationMode.GUI)) {
throw new BeaconServerException(PlatformState.getLastError());
}
}
LauncherInput.handle(msg.getArguments());
return Response.builder().build();
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.StatusExchange;
import java.io.IOException;
public class StatusExchangeImpl extends StatusExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
String mode;
if (OperationMode.get() == null) {
mode = "none";
} else {
mode = OperationMode.get().getId();
}
return Response.builder().mode(mode).build();
}
}

View file

@ -0,0 +1,22 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.StopExchange;
import java.io.IOException;
public class StopExchangeImpl extends StopExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
ThreadHelper.runAsync(() -> {
ThreadHelper.sleep(1000);
OperationMode.close();
});
return Response.builder().success(true).build();
}
}

View file

@ -0,0 +1,17 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.TerminalLaunchExchange;
import java.io.IOException;
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var r = TerminalLauncherManager.performLaunch(msg.getRequest());
return Response.builder().targetFile(r).build();
}
}

View file

@ -0,0 +1,17 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.TerminalWaitExchange;
import java.io.IOException;
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
TerminalLauncherManager.waitForCompletion(msg.getRequest());
return Response.builder().build();
}
}

View file

@ -1,14 +1,17 @@
package io.xpipe.app.exchange; package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppProperties;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.exchange.cli.VersionExchange; import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.VersionExchange;
public class VersionExchangeImpl extends VersionExchange import java.io.IOException;
implements MessageExchangeImpl<VersionExchange.Request, VersionExchange.Response> {
public class VersionExchangeImpl extends VersionExchange {
@Override @Override
public Response handleRequest(BeaconHandler handler, Request msg) { public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var jvmVersion = System.getProperty("java.vm.vendor") + " " var jvmVersion = System.getProperty("java.vm.vendor") + " "
+ System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.name") + " ("
+ System.getProperty("java.vm.version") + ")"; + System.getProperty("java.vm.version") + ")";

View file

@ -50,11 +50,10 @@ public class AppProperties {
Properties props = new Properties(); Properties props = new Properties();
props.load(Files.newInputStream(propsFile)); props.load(Files.newInputStream(propsFile));
props.forEach((key, value) -> { props.forEach((key, value) -> {
if (System.getProperty(key.toString()) != null) { // Don't overwrite existing properties
return; if (System.getProperty(key.toString()) == null) {
System.setProperty(key.toString(), value.toString());
} }
System.setProperty(key.toString(), value.toString());
}); });
} catch (IOException e) { } catch (IOException e) {
ErrorEvent.fromThrowable(e).handle(); ErrorEvent.fromThrowable(e).handle();

View file

@ -1,358 +0,0 @@
package io.xpipe.app.core;
import io.xpipe.app.exchange.MessageExchangeImpls;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.beacon.*;
import io.xpipe.beacon.exchange.MessageExchanges;
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
import io.xpipe.core.util.Deobfuscator;
import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.JacksonMapper;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HexFormat;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
public class AppSocketServer {
private static AppSocketServer INSTANCE;
private final int port;
private ServerSocket socket;
private boolean running;
private int connectionCounter;
private Thread listenerThread;
private AppSocketServer(int port) {
this.port = port;
}
public static void init() {
int port = -1;
try {
port = BeaconConfig.getUsedPort();
INSTANCE = new AppSocketServer(port);
INSTANCE.createSocketListener();
TrackEvent.withInfo("Initialized socket server")
.tag("port", port)
.build()
.handle();
} catch (Exception ex) {
// Not terminal!
ErrorEvent.fromThrowable(ex)
.description("Unable to start local socket server on port " + port)
.build()
.handle();
}
}
public static void reset() {
if (INSTANCE != null) {
INSTANCE.stop();
INSTANCE = null;
}
}
private void stop() {
if (!running) {
return;
}
running = false;
try {
socket.close();
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
try {
listenerThread.join();
} catch (InterruptedException ignored) {
}
}
private void createSocketListener() throws IOException {
socket = new ServerSocket(port, 10000, InetAddress.getLoopbackAddress());
running = true;
listenerThread = new Thread(
() -> {
while (running) {
Socket clientSocket;
try {
clientSocket = socket.accept();
} catch (Exception ex) {
continue;
}
try {
performExchangesAsync(clientSocket);
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).build().handle();
}
connectionCounter++;
}
},
"socket server");
listenerThread.start();
}
private boolean performExchange(Socket clientSocket, int id) throws Exception {
if (clientSocket.isClosed()) {
TrackEvent.trace("Socket closed");
return false;
}
JsonNode node;
try (InputStream blockIn = BeaconFormat.readBlocks(clientSocket.getInputStream())) {
node = JacksonMapper.getDefault().readTree(blockIn);
}
if (node.isMissingNode()) {
TrackEvent.trace("Received EOF");
return false;
}
TrackEvent.trace("Received raw request: \n" + node.toPrettyString());
var req = parseRequest(node);
TrackEvent.trace("Parsed request: \n" + req.toString());
var prov = MessageExchangeImpls.byRequest(req);
if (prov.isEmpty()) {
throw new IllegalArgumentException("Unknown request id: " + req.getClass());
}
AtomicReference<FailableRunnable<Exception>> post = new AtomicReference<>();
var res = prov.get()
.handleRequest(
new BeaconHandler() {
@Override
public void postResponse(FailableRunnable<Exception> r) {
post.set(r);
}
@Override
public OutputStream sendBody() throws IOException {
TrackEvent.trace("Starting writing body for #" + id);
return AppSocketServer.this.sendBody(clientSocket);
}
@Override
public InputStream receiveBody() throws IOException {
TrackEvent.trace("Starting to read body for #" + id);
return AppSocketServer.this.receiveBody(clientSocket);
}
},
req);
TrackEvent.trace("Sending response to #" + id + ": \n" + res.toString());
AppSocketServer.this.sendResponse(clientSocket, res);
try {
// If this fails, we sadly can't send an error response. Therefore just report it on the server side
if (post.get() != null) {
post.get().run();
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
TrackEvent.builder()
.type("trace")
.message("Socket connection #" + id + " performed exchange "
+ req.getClass().getSimpleName())
.build()
.handle();
return true;
}
private void performExchanges(Socket clientSocket, int id) {
try {
JsonNode informationNode;
try (InputStream blockIn = BeaconFormat.readBlocks(clientSocket.getInputStream())) {
informationNode = JacksonMapper.getDefault().readTree(blockIn);
}
if (informationNode.isMissingNode()) {
TrackEvent.trace("Received EOF");
return;
}
var information =
JacksonMapper.getDefault().treeToValue(informationNode, BeaconClient.ClientInformation.class);
try (var blockOut = BeaconFormat.writeBlocks(clientSocket.getOutputStream())) {
blockOut.write("\"ACK\"".getBytes(StandardCharsets.UTF_8));
}
TrackEvent.builder()
.type("trace")
.message("Created new socket connection #" + id)
.tag("client", information != null ? information.toDisplayString() : "Unknown")
.build()
.handle();
try {
while (true) {
if (!performExchange(clientSocket, id)) {
break;
}
}
TrackEvent.builder()
.type("trace")
.message("Socket connection #" + id + " finished successfully")
.build()
.handle();
} catch (ClientException ce) {
TrackEvent.trace("Sending client error to #" + id + ": " + ce.getMessage());
sendClientErrorResponse(clientSocket, ce.getMessage());
} catch (ServerException se) {
TrackEvent.trace("Sending server error to #" + id + ": " + se.getMessage());
Deobfuscator.deobfuscate(se);
sendServerErrorResponse(clientSocket, se);
var toReport = se.getCause() != null ? se.getCause() : se;
ErrorEvent.fromThrowable(toReport).build().handle();
} catch (SocketException ex) {
// Do not send error and omit it, as this might happen often
// This is expected if you kill a running xpipe CLI process
// We do not send the error to the client as the socket connection might be broken
ErrorEvent.fromThrowable(ex).omitted(true).expected().build().handle();
} catch (Throwable ex) {
TrackEvent.trace("Sending internal server error to #" + id + ": " + ex.getMessage());
Deobfuscator.deobfuscate(ex);
sendServerErrorResponse(clientSocket, ex);
ErrorEvent.fromThrowable(ex).build().handle();
}
} catch (SocketException ex) {
// Omit it, as this might happen often
// This is expected if you kill a running xpipe CLI process
ErrorEvent.fromThrowable(ex).expected().omit().build().handle();
} catch (Throwable ex) {
ErrorEvent.fromThrowable(ex).build().handle();
} finally {
try {
clientSocket.close();
TrackEvent.trace("Closed socket #" + id);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).build().handle();
}
}
TrackEvent.builder().type("trace").message("Socket connection #" + id + " finished unsuccessfully");
}
private void performExchangesAsync(Socket clientSocket) {
var id = connectionCounter;
var t = new Thread(
() -> {
performExchanges(clientSocket, id);
},
"socket connection #" + id);
t.start();
}
public OutputStream sendBody(Socket outSocket) throws IOException {
outSocket.getOutputStream().write(BeaconConfig.BODY_SEPARATOR);
return BeaconFormat.writeBlocks(outSocket.getOutputStream());
}
public InputStream receiveBody(Socket outSocket) throws IOException {
var read = outSocket.getInputStream().readNBytes(BeaconConfig.BODY_SEPARATOR.length);
if (!Arrays.equals(read, BeaconConfig.BODY_SEPARATOR)) {
throw new IOException("Expected body start (" + HexFormat.of().formatHex(BeaconConfig.BODY_SEPARATOR)
+ ") but got " + HexFormat.of().formatHex(read));
}
return BeaconFormat.readBlocks(outSocket.getInputStream());
}
public <T extends ResponseMessage> void sendResponse(Socket outSocket, T obj) throws Exception {
ObjectNode json = JacksonMapper.getDefault().valueToTree(obj);
var prov = MessageExchanges.byResponse(obj).get();
json.set("messageType", new TextNode(prov.getId()));
json.set("messagePhase", new TextNode("response"));
var msg = JsonNodeFactory.instance.objectNode();
msg.set("xPipeMessage", json);
var writer = new StringWriter();
var mapper = JacksonMapper.getDefault();
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
g.writeTree(msg);
} catch (IOException ex) {
throw new ConnectorException("Couldn't serialize request", ex);
}
var content = writer.toString();
TrackEvent.trace("Sending raw response:\n" + content);
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
blockOut.write(content.getBytes(StandardCharsets.UTF_8));
}
}
public void sendClientErrorResponse(Socket outSocket, String message) throws Exception {
var err = new ClientErrorMessage(message);
ObjectNode json = JacksonMapper.getDefault().valueToTree(err);
var msg = JsonNodeFactory.instance.objectNode();
msg.set("xPipeClientError", json);
// Don't log this as it clutters the output
// TrackEvent.trace("beacon", "Sending raw client error:\n" + json.toPrettyString());
var mapper = JacksonMapper.getDefault();
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
var gen = mapper.createGenerator(blockOut);
gen.writeTree(msg);
}
}
public void sendServerErrorResponse(Socket outSocket, Throwable ex) throws Exception {
var err = new ServerErrorMessage(UUID.randomUUID(), ex);
ObjectNode json = JacksonMapper.getDefault().valueToTree(err);
var msg = JsonNodeFactory.instance.objectNode();
msg.set("xPipeServerError", json);
// Don't log this as it clutters the output
// TrackEvent.trace("beacon", "Sending raw server error:\n" + json.toPrettyString());
var mapper = JacksonMapper.getDefault();
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
var gen = mapper.createGenerator(blockOut);
gen.writeTree(msg);
}
}
private <T extends RequestMessage> T parseRequest(JsonNode header) throws Exception {
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
TrackEvent.trace("Parsed raw request:\n" + content.toPrettyString());
var type = content.required("messageType").textValue();
var phase = content.required("messagePhase").textValue();
if (!phase.equals("request")) {
throw new IllegalArgumentException("Not a request");
}
content.remove("messageType");
content.remove("messagePhase");
var prov = MessageExchangeImpls.byId(type);
if (prov.isEmpty()) {
throw new IllegalArgumentException("Unknown request id: " + type);
}
var reader = JacksonMapper.getDefault().readerFor(prov.get().getRequestClass());
return reader.readValue(content);
}
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.core.mode; package io.xpipe.app.core.mode;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.browser.session.BrowserSessionModel; import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.*; import io.xpipe.app.core.*;
@ -17,7 +18,6 @@ import io.xpipe.app.util.FileBridge;
import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LocalShell; import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.UnlockAlert; import io.xpipe.app.util.UnlockAlert;
import io.xpipe.core.util.JacksonMapper;
public class BaseMode extends OperationMode { public class BaseMode extends OperationMode {
@ -43,12 +43,8 @@ public class BaseMode extends OperationMode {
// if (true) throw new IllegalStateException(); // if (true) throw new IllegalStateException();
TrackEvent.info("Initializing base mode components ..."); TrackEvent.info("Initializing base mode components ...");
AppExtensionManager.init(true);
JacksonMapper.initModularized(AppExtensionManager.getInstance().getExtendedLayer());
AppI18n.init(); AppI18n.init();
LicenseProvider.get().init(); LicenseProvider.get().init();
AppPrefs.initLocal();
AppI18n.init();
AppCertutilCheck.check(); AppCertutilCheck.check();
AppAvCheck.check(); AppAvCheck.check();
AppSid.init(); AppSid.init();
@ -56,8 +52,8 @@ public class BaseMode extends OperationMode {
AppShellCheck.check(); AppShellCheck.check();
XPipeDistributionType.init(); XPipeDistributionType.init();
AppPrefs.setDefaults(); AppPrefs.setDefaults();
// Initialize socket server as we should be prepared for git askpass commands // Initialize beacon server as we should be prepared for git askpass commands
AppSocketServer.init(); AppBeaconServer.init();
GitStorageHandler.getInstance().init(); GitStorageHandler.getInstance().init();
GitStorageHandler.getInstance().setupRepositoryAndPull(); GitStorageHandler.getInstance().setupRepositoryAndPull();
AppPrefs.initSharedRemote(); AppPrefs.initSharedRemote();
@ -85,8 +81,8 @@ public class BaseMode extends OperationMode {
AppResources.reset(); AppResources.reset();
AppExtensionManager.reset(); AppExtensionManager.reset();
AppDataLock.unlock(); AppDataLock.unlock();
// Shut down socket server last to keep a non-daemon thread running // Shut down server last to keep a non-daemon thread running
AppSocketServer.reset(); AppBeaconServer.reset();
TrackEvent.info("Background mode shutdown finished"); TrackEvent.info("Background mode shutdown finished");
} }
} }

View file

@ -6,6 +6,7 @@ import io.xpipe.app.core.check.AppTempCheck;
import io.xpipe.app.core.check.AppUserDirectoryCheck; import io.xpipe.app.core.check.AppUserDirectoryCheck;
import io.xpipe.app.issue.*; import io.xpipe.app.issue.*;
import io.xpipe.app.launcher.LauncherCommand; import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.LocalShell; import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
@ -109,6 +110,9 @@ public abstract class OperationMode {
AppProperties.logArguments(args); AppProperties.logArguments(args);
AppProperties.logSystemProperties(); AppProperties.logSystemProperties();
AppProperties.logPassedProperties(); AppProperties.logPassedProperties();
AppExtensionManager.init(true);
AppI18n.init();
AppPrefs.initLocal();
TrackEvent.info("Finished initial setup"); TrackEvent.info("Finished initial setup");
} catch (Throwable ex) { } catch (Throwable ex) {
ErrorEvent.fromThrowable(ex).term().handle(); ErrorEvent.fromThrowable(ex).term().handle();

View file

@ -1,76 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.DialogExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.DialogReference;
import io.xpipe.core.util.FailableConsumer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class DialogExchangeImpl extends DialogExchange
implements MessageExchangeImpl<DialogExchange.Request, DialogExchange.Response> {
private static final Map<UUID, Dialog> openDialogs = new HashMap<>();
private static final Map<UUID, FailableConsumer<?, Exception>> openDialogConsumers = new HashMap<>();
public static <T> DialogReference add(Dialog d, FailableConsumer<T, Exception> onCompletion) throws Exception {
return add(d, UUID.randomUUID(), onCompletion);
}
public static <T> DialogReference add(Dialog d, UUID uuid, FailableConsumer<T, Exception> onCompletion)
throws Exception {
openDialogs.put(uuid, d);
openDialogConsumers.put(uuid, onCompletion);
return new DialogReference(uuid, d.start());
}
@Override
public DialogExchange.Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
if (msg.isCancel()) {
TrackEvent.withTrace("Received cancel dialog request")
.tag("key", msg.getDialogKey())
.handle();
openDialogs.remove(msg.getDialogKey());
openDialogConsumers.remove(msg.getDialogKey());
return DialogExchange.Response.builder().element(null).build();
}
var dialog = openDialogs.get(msg.getDialogKey());
var e = dialog.receive(msg.getValue());
TrackEvent.withTrace("Received normal dialog request")
.tag("key", msg.getDialogKey())
.tag("value", msg.getValue())
.tag("newElement", e)
.handle();
if (e == null) {
openDialogs.remove(msg.getDialogKey());
var con = openDialogConsumers.remove(msg.getDialogKey());
con.accept(dialog.getResult());
}
return DialogExchange.Response.builder().element(e).build();
//
//
// var provider = getProvider(msg.getInstance().getProvider());
// var completeConfig = toCompleteConfig(provider);
//
// var option = completeConfig.keySet().stream()
// .filter(o -> o.getKey().equals(msg.getKey())).findAny()
// .orElseThrow(() -> new ClientException("Invalid config key: " + msg.getKey()));
//
// String errorMsg = null;
// try {
// option.getConverter().convertFromString(msg.getValue());
// } catch (Exception ex) {
// errorMsg = ex.getMessage();
// }
//
// return DialogExchange.Response.builder().errorMsg(errorMsg).build();
}
}

View file

@ -1,15 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.FocusExchange;
public class FocusExchangeImpl extends FocusExchange
implements MessageExchangeImpl<FocusExchange.Request, FocusExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
OperationMode.switchUp(OperationMode.map(msg.getMode()));
return Response.builder().build();
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.LaunchExchange;
import io.xpipe.core.store.LaunchableStore;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LaunchExchangeImpl extends LaunchExchange
implements MessageExchangeImpl<LaunchExchange.Request, LaunchExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var store = getStoreEntryById(msg.getId(), false);
if (store.getStore() instanceof LaunchableStore s) {
// var command = s.prepareLaunchCommand()
// .prepareTerminalOpen(TerminalInitScriptConfig.ofName(store.getName()), sc -> null);
// return Response.builder().command(split(command)).build();
}
throw new IllegalArgumentException(store.getName() + " is not launchable");
}
private List<String> split(String command) {
var split = Arrays.stream(command.split(" ", 3)).collect(Collectors.toList());
var s = split.get(2);
if ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))) {
split.set(2, s.substring(1, s.length() - 1));
}
return split;
}
}

View file

@ -1,47 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.store.DataStoreId;
import lombok.NonNull;
public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends ResponseMessage> extends MessageExchange {
default DataStoreEntry getStoreEntryByName(@NonNull String name, boolean acceptDisabled) throws ClientException {
var store = DataStorage.get().getStoreEntryIfPresent(name);
if (store.isEmpty()) {
throw new ClientException("No store with name " + name + " was found");
}
if (store.get().isDisabled() && !acceptDisabled) {
throw new ClientException(
String.format("Store %s is disabled", store.get().getName()));
}
return store.get();
}
default DataStoreEntry getStoreEntryById(@NonNull DataStoreId id, boolean acceptUnusable) throws ClientException {
var store = DataStorage.get().getStoreEntryIfPresent(id);
if (store.isEmpty()) {
throw new ClientException("No store with id " + id + " was found");
}
if (store.get().isDisabled() && !acceptUnusable) {
throw new ClientException(
String.format("Store %s is disabled", store.get().getName()));
}
if (!store.get().getValidity().isUsable() && !acceptUnusable) {
throw new ClientException(String.format(
"Store %s is not completely configured", store.get().getName()));
}
return store.get();
}
String getId();
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;
}

View file

@ -1,61 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchanges;
import io.xpipe.core.util.ModuleLayerLoader;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public class MessageExchangeImpls {
private static List<MessageExchangeImpl<?, ?>> ALL;
@SuppressWarnings("unchecked")
public static <RQ extends RequestMessage, RS extends ResponseMessage> Optional<MessageExchangeImpl<RQ, RS>> byId(
String name) {
var r = ALL.stream().filter(d -> d.getId().equals(name)).findAny();
return Optional.ofNullable((MessageExchangeImpl<RQ, RS>) r.orElse(null));
}
@SuppressWarnings("unchecked")
public static <RQ extends RequestMessage, RS extends ResponseMessage>
Optional<MessageExchangeImpl<RQ, RS>> byRequest(RQ req) {
var r = ALL.stream()
.filter(d -> d.getRequestClass().equals(req.getClass()))
.findAny();
return Optional.ofNullable((MessageExchangeImpl<RQ, RS>) r.orElse(null));
}
public static List<MessageExchangeImpl<?, ?>> getAll() {
return ALL;
}
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
ALL = ServiceLoader.load(layer, MessageExchangeImpl.class).stream()
.map(s -> {
// TrackEvent.trace("init", "Loaded exchange implementation " + ex.getId());
return (MessageExchangeImpl<?, ?>) s.get();
})
.collect(Collectors.toList());
ALL.forEach(messageExchange -> {
if (MessageExchanges.byId(messageExchange.getId()).isEmpty()) {
throw new AssertionError("Missing base exchange: " + messageExchange.getId());
}
});
MessageExchanges.getAll().forEach(messageExchange -> {
if (MessageExchangeImpls.byId(messageExchange.getId()).isEmpty()) {
throw new AssertionError("Missing exchange implementation: " + messageExchange.getId());
}
});
}
}
}

View file

@ -1,30 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.launcher.LauncherInput;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ServerException;
import io.xpipe.beacon.exchange.OpenExchange;
public class OpenExchangeImpl extends OpenExchange
implements MessageExchangeImpl<OpenExchange.Request, OpenExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws ServerException {
if (msg.getArguments().isEmpty()) {
if (!OperationMode.switchToSyncIfPossible(OperationMode.GUI)) {
throw new ServerException(PlatformState.getLastError());
}
}
// Wait for startup
while (OperationMode.get() == null) {
ThreadHelper.sleep(100);
}
LauncherInput.handle(msg.getArguments());
return Response.builder().build();
}
}

View file

@ -1,23 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.QueryStoreExchange;
import io.xpipe.core.dialog.DialogMapper;
public class QueryStoreExchangeImpl extends QueryStoreExchange
implements MessageExchangeImpl<QueryStoreExchange.Request, QueryStoreExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var store = getStoreEntryByName(msg.getName(), true);
var summary = "";
var dialog = store.getProvider().dialogForStore(store.getStore().asNeeded());
var config = new DialogMapper(dialog).handle();
return Response.builder()
.summary(summary)
.internalStore(store.getStore())
.provider(store.getProvider().getId())
.config(config)
.build();
}
}

View file

@ -1,16 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.TerminalLaunchExchange;
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange
implements MessageExchangeImpl<TerminalLaunchExchange.Request, TerminalLaunchExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws ClientException {
var r = TerminalLauncherManager.performLaunch(msg.getRequest());
return Response.builder().targetFile(r).build();
}
}

View file

@ -1,17 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.ServerException;
import io.xpipe.beacon.exchange.TerminalWaitExchange;
public class TerminalWaitExchangeImpl extends TerminalWaitExchange
implements MessageExchangeImpl<TerminalWaitExchange.Request, TerminalWaitExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws ServerException, ClientException {
TerminalLauncherManager.waitForCompletion(msg.getRequest());
return Response.builder().build();
}
}

View file

@ -1,30 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.DrainExchange;
import io.xpipe.core.store.ShellStore;
public class DrainExchangeImpl extends DrainExchange
implements MessageExchangeImpl<DrainExchange.Request, DrainExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getStoreEntryById(msg.getSource(), false);
if (!(ds.getStore() instanceof ShellStore)) {
throw new ClientException("Can't open file system for connection");
}
handler.postResponse(() -> {
ShellStore store = ds.getStore().asNeeded();
try (var fs = store.createFileSystem();
var output = handler.sendBody();
var inputStream = fs.openInput(msg.getPath())) {
inputStream.transferTo(output);
}
});
return Response.builder().build();
}
}

View file

@ -1,21 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.DialogExchangeImpl;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.EditStoreExchange;
import io.xpipe.core.store.DataStore;
public class EditStoreExchangeImpl extends EditStoreExchange
implements MessageExchangeImpl<EditStoreExchange.Request, EditStoreExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var s = getStoreEntryByName(msg.getName(), false);
var dialog = s.getProvider().dialogForStore(s.getStore());
var reference = DialogExchangeImpl.add(dialog, (DataStore newStore) -> {
// s.setStore(newStore);
});
return Response.builder().dialog(reference).build();
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.ListStoresExchange;
import io.xpipe.beacon.exchange.data.StoreListEntry;
import java.util.Comparator;
import java.util.List;
public class ListStoresExchangeImpl extends ListStoresExchange
implements MessageExchangeImpl<ListStoresExchange.Request, ListStoresExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
DataStorage s = DataStorage.get();
if (s == null) {
return Response.builder().entries(List.of()).build();
}
var e = s.getStoreEntries().stream()
.filter(entry -> !entry.isDisabled())
.map(col -> StoreListEntry.builder()
.id(DataStorage.get().getId(col))
.type(col.getProvider().getId())
.build())
.sorted(Comparator.comparing(en -> en.getId().toString()))
.toList();
return Response.builder().entries(e).build();
}
}

View file

@ -1,36 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.ModeExchange;
public class ModeExchangeImpl extends ModeExchange
implements MessageExchangeImpl<ModeExchange.Request, ModeExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
// Wait for startup
while (OperationMode.get() == null) {
ThreadHelper.sleep(100);
}
var mode = OperationMode.map(msg.getMode());
if (!mode.isSupported()) {
throw new ClientException("Unsupported mode: " + msg.getMode().getDisplayName() + ". Supported: "
+ String.join(
", ",
OperationMode.getAll().stream()
.filter(OperationMode::isSupported)
.map(OperationMode::getId)
.toList()));
}
OperationMode.switchToSyncIfPossible(mode);
return ModeExchange.Response.builder()
.usedMode(OperationMode.map(OperationMode.get()))
.build();
}
}

View file

@ -1,14 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.ReadDrainExchange;
public class ReadDrainExchangeImpl extends ReadDrainExchange
implements MessageExchangeImpl<ReadDrainExchange.Request, ReadDrainExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
return ReadDrainExchange.Response.builder().build();
}
}

View file

@ -1,23 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.RemoveStoreExchange;
import io.xpipe.core.store.DataStoreId;
public class RemoveStoreExchangeImpl extends RemoveStoreExchange
implements MessageExchangeImpl<RemoveStoreExchange.Request, RemoveStoreExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var s = getStoreEntryById(DataStoreId.fromString(msg.getStoreName()), true);
if (!s.getConfiguration().isDeletable()) {
throw new ClientException("Store is not deletable");
}
DataStorage.get().deleteStoreEntry(s);
return Response.builder().build();
}
}

View file

@ -1,18 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.RenameStoreExchange;
import io.xpipe.core.store.DataStoreId;
public class RenameStoreExchangeImpl extends RenameStoreExchange
implements MessageExchangeImpl<RenameStoreExchange.Request, RenameStoreExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws ClientException {
var s = getStoreEntryById(DataStoreId.fromString(msg.getStoreName()), true);
s.setName(msg.getNewName());
return Response.builder().build();
}
}

View file

@ -1,29 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.SinkExchange;
import io.xpipe.core.store.ShellStore;
public class SinkExchangeImpl extends SinkExchange
implements MessageExchangeImpl<SinkExchange.Request, SinkExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getStoreEntryById(msg.getSource(), false);
if (!(ds.getStore() instanceof ShellStore)) {
throw new ClientException("Can't open file system for connection");
}
ShellStore store = ds.getStore().asNeeded();
try (var fs = store.createFileSystem();
var inputStream = handler.receiveBody();
var output = fs.openOutput(msg.getPath(), -1)) {
inputStream.transferTo(output);
}
return Response.builder().build();
}
}

View file

@ -1,22 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.StatusExchange;
public class StatusExchangeImpl extends StatusExchange
implements MessageExchangeImpl<StatusExchange.Request, StatusExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
String mode;
if (OperationMode.get() == null) {
mode = "none";
} else {
mode = OperationMode.get().getId();
}
return Response.builder().mode(mode).build();
}
}

View file

@ -1,22 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.StopExchange;
public class StopExchangeImpl extends StopExchange
implements MessageExchangeImpl<StopExchange.Request, StopExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
handler.postResponse(() -> {
ThreadHelper.runAsync(() -> {
ThreadHelper.sleep(1000);
OperationMode.close();
});
});
return Response.builder().success(true).build();
}
}

View file

@ -1,141 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.DialogExchangeImpl;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.StoreAddExchange;
import io.xpipe.core.dialog.Choice;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import lombok.SneakyThrows;
import java.util.List;
public class StoreAddExchangeImpl extends StoreAddExchange
implements MessageExchangeImpl<StoreAddExchange.Request, StoreAddExchange.Response> {
@Override
@SneakyThrows
public StoreAddExchange.Response handleRequest(BeaconHandler handler, Request msg) {
Dialog creatorDialog;
DataStoreProvider provider;
if (msg.getStoreInput() != null) {
creatorDialog = Dialog.empty().evaluateTo(msg::getStoreInput);
provider = null;
} else {
if (msg.getType() == null) {
throw new ClientException("Missing data store tight");
}
provider = DataStoreProviders.byName(msg.getType()).orElseThrow(() -> {
return new ClientException("Unrecognized data store type: " + msg.getType());
});
creatorDialog = provider.dialogForStore(provider.defaultStore());
}
var name = new SimpleStringProperty(msg.getName());
var completeDialog = createCompleteDialog(provider, creatorDialog, name);
var config = DialogExchangeImpl.add(completeDialog, (DataStore store) -> {
if (store == null) {
return;
}
DataStorage.get().addStoreIfNotPresent(name.getValue(), store);
});
return StoreAddExchange.Response.builder().config(config).build();
}
private Dialog createCompleteDialog(DataStoreProvider provider, Dialog creator, StringProperty name) {
var validator = Dialog.header(() -> {
DataStore store = creator.getResult();
if (store == null) {
return "Store is null";
}
return null;
})
.map((String msg) -> {
return msg == null ? creator.getResult() : null;
});
var creatorAndValidator = Dialog.chain(creator, Dialog.busy(), validator);
var nameQ = Dialog.retryIf(
Dialog.query("Store name", true, true, false, name.getValue(), QueryConverter.STRING),
(String r) -> {
return DataStorage.get().getStoreEntryIfPresent(r).isPresent()
? "Store with name " + r + " already exists"
: null;
})
.onCompletion((String n) -> name.setValue(n));
var display = Dialog.header(() -> {
if (provider == null) {
return "Successfully created data store " + name.get();
}
DataStore s = creator.getResult();
String d = "";
d = d.indent(2);
return "Successfully created data store " + name.get() + ":\n" + d;
});
if (provider == null) {
return Dialog.chain(
creatorAndValidator, Dialog.skipIf(display, () -> creatorAndValidator.getResult() == null))
.evaluateTo(creatorAndValidator);
}
var aborted = new SimpleBooleanProperty();
var addStore =
Dialog.skipIf(Dialog.chain(nameQ, display), () -> aborted.get() || validator.getResult() == null);
var prop = new SimpleObjectProperty<Dialog>();
var fork = Dialog.skipIf(
Dialog.fork(
"Choose how to continue",
List.of(
new Choice('r', "Retry"),
new Choice('i', "Ignore and continue"),
new Choice('e', "Edit configuration"),
new Choice('a', "Abort")),
true,
0,
(Integer choice) -> {
if (choice == 0) {
return Dialog.chain(Dialog.busy(), validator, prop.get());
}
if (choice == 1) {
return null;
}
if (choice == 2) {
return Dialog.chain(creatorAndValidator, prop.get());
}
if (choice == 3) {
aborted.set(true);
return null;
}
throw new AssertionError();
})
.evaluateTo(() -> null),
() -> validator.getResult() != null);
prop.set(fork);
return Dialog.chain(creatorAndValidator, fork, addStore)
.evaluateTo(() -> aborted.get() ? null : creator.getResult());
}
}

View file

@ -1,37 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.StoreProviderListExchange;
import io.xpipe.beacon.exchange.data.ProviderEntry;
import java.util.Arrays;
import java.util.stream.Collectors;
public class StoreProviderListExchangeImpl extends StoreProviderListExchange
implements MessageExchangeImpl<StoreProviderListExchange.Request, StoreProviderListExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
var categories = DataStoreProvider.CreationCategory.values();
var all = DataStoreProviders.getAll();
var map = Arrays.stream(categories)
.collect(Collectors.toMap(category -> getName(category), category -> all.stream()
.filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory()))
.map(p -> ProviderEntry.builder()
.id(p.getId())
.description(p.displayDescription().getValue())
.hidden(p.getCreationCategory() == null)
.build())
.toList()));
return Response.builder().entries(map).build();
}
private String getName(DataStoreProvider.CreationCategory category) {
return category.name().substring(0, 1).toUpperCase()
+ category.name().substring(1).toLowerCase();
}
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.launcher; package io.xpipe.app.launcher;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.core.AppDataLock; import io.xpipe.app.core.AppDataLock;
import io.xpipe.app.core.AppLogs; import io.xpipe.app.core.AppLogs;
import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppProperties;
@ -9,13 +10,13 @@ import io.xpipe.app.issue.LogErrorHandler;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconServer; import io.xpipe.beacon.BeaconClient;
import io.xpipe.beacon.exchange.FocusExchange; import io.xpipe.beacon.BeaconClientInformation;
import io.xpipe.beacon.exchange.OpenExchange; import io.xpipe.beacon.api.FocusExchange;
import io.xpipe.beacon.api.OpenExchange;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeDaemonMode; import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import picocli.CommandLine; import picocli.CommandLine;
@ -81,26 +82,25 @@ public class LauncherCommand implements Callable<Integer> {
private void checkStart() { private void checkStart() {
try { try {
if (BeaconServer.isReachable()) { var port = AppBeaconServer.get().getPort();
try (var con = new LauncherConnection()) { var client = BeaconClient.tryEstablishConnection(port, BeaconClientInformation.DaemonInformation.builder().build());
con.constructSocket(); if (client.isPresent()) {
con.performSimpleExchange(FocusExchange.Request.builder() client.get().performRequest(FocusExchange.Request.builder().mode(getEffectiveMode()).build());
.mode(getEffectiveMode())
.build());
if (!inputs.isEmpty()) { if (!inputs.isEmpty()) {
con.performSimpleExchange( client.get().performRequest(
OpenExchange.Request.builder().arguments(inputs).build()); OpenExchange.Request.builder().arguments(inputs).build());
} }
if (OsType.getLocal().equals(OsType.MACOS)) { if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> { Desktop.getDesktop().setOpenURIHandler(e -> {
con.performSimpleExchange(OpenExchange.Request.builder() try {
.arguments(List.of(e.getURI().toString())) client.get().performRequest(OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
.build()); } catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle();
}
}); });
ThreadHelper.sleep(1000); ThreadHelper.sleep(1000);
} }
}
TrackEvent.info("Another instance is already running on this port. Quitting ..."); TrackEvent.info("Another instance is already running on this port. Quitting ...");
OperationMode.halt(1); OperationMode.halt(1);
} }

View file

@ -1,18 +0,0 @@
package io.xpipe.app.launcher;
import io.xpipe.beacon.BeaconClient;
import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException;
public class LauncherConnection extends BeaconConnection {
@Override
protected void constructSocket() {
try {
beaconClient = BeaconClient.establishConnection(
BeaconClient.DaemonInformation.builder().build());
} catch (Exception ex) {
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
}
}
}

View file

@ -14,14 +14,13 @@ import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue; import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
@ -121,6 +120,13 @@ public class AppPrefs {
private final StringProperty lockCrypt = private final StringProperty lockCrypt =
mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class); mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class);
final Property<Integer> httpServerPort =
map(new SimpleObjectProperty<>(XPipeInstallation.getDefaultBeaconPort()), "httpServerPort", Integer.class);
public ObservableValue<Integer> httpServerPort() {
return httpServerPort;
}
private final IntegerProperty editorReloadTimeout = private final IntegerProperty editorReloadTimeout =
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class); map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
private final BooleanProperty confirmDeletions = private final BooleanProperty confirmDeletions =
@ -153,6 +159,7 @@ public class AppPrefs {
new SshCategory(), new SshCategory(),
new LocalShellCategory(), new LocalShellCategory(),
new SecurityCategory(), new SecurityCategory(),
new HttpServerCategory(),
new TroubleshootCategory(), new TroubleshootCategory(),
new DeveloperCategory()) new DeveloperCategory())
.filter(appPrefsCategory -> appPrefsCategory.show()) .filter(appPrefsCategory -> appPrefsCategory.show())

View file

@ -0,0 +1,26 @@
package io.xpipe.app.prefs;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.util.OptionsBuilder;
public class HttpServerCategory extends AppPrefsCategory {
@Override
protected String getId() {
return "httpServer";
}
@Override
protected Comp<?> create() {
var prefs = AppPrefs.get();
return new OptionsBuilder()
.addTitle("httpServerConfiguration")
.sub(new OptionsBuilder()
.nameAndDescription("httpServerPort")
.addInteger(prefs.httpServerPort)
.disable(AppBeaconServer.get().isPropertyPort())
)
.buildComp();
}
}

View file

@ -1,13 +1,12 @@
package io.xpipe.app.util; package io.xpipe.app.util;
import io.xpipe.beacon.ClientException; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.ServerException; import io.xpipe.beacon.BeaconServerException;
import io.xpipe.core.process.ProcessControl; import io.xpipe.core.process.ProcessControl;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.TerminalInitScriptConfig; import io.xpipe.core.process.TerminalInitScriptConfig;
import io.xpipe.core.process.WorkingDirectoryFunction; import io.xpipe.core.process.WorkingDirectoryFunction;
import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FilePath;
import lombok.Setter; import lombok.Setter;
import lombok.Value; import lombok.Value;
import lombok.experimental.NonFinal; import lombok.experimental.NonFinal;
@ -73,10 +72,10 @@ public class TerminalLauncherManager {
return latch; return latch;
} }
public static Path waitForCompletion(UUID request) throws ClientException, ServerException { public static Path waitForCompletion(UUID request) throws BeaconClientException, BeaconServerException {
var e = entries.get(request); var e = entries.get(request);
if (e == null) { if (e == null) {
throw new ClientException("Unknown launch request " + request); throw new BeaconClientException("Unknown launch request " + request);
} }
while (true) { while (true) {
@ -89,21 +88,21 @@ public class TerminalLauncherManager {
if (r instanceof ResultFailure failure) { if (r instanceof ResultFailure failure) {
entries.remove(request); entries.remove(request);
var t = failure.getThrowable(); var t = failure.getThrowable();
throw new ServerException(t); throw new BeaconServerException(t);
} }
return ((ResultSuccess) r).getTargetScript(); return ((ResultSuccess) r).getTargetScript();
} }
} }
public static Path performLaunch(UUID request) throws ClientException { public static Path performLaunch(UUID request) throws BeaconClientException {
var e = entries.remove(request); var e = entries.remove(request);
if (e == null) { if (e == null) {
throw new ClientException("Unknown launch request " + request); throw new BeaconClientException("Unknown launch request " + request);
} }
if (!(e.result instanceof ResultSuccess)) { if (!(e.result instanceof ResultSuccess)) {
throw new ClientException("Invalid launch request state " + request); throw new BeaconClientException("Invalid launch request state " + request);
} }
return ((ResultSuccess) e.getResult()).getTargetScript(); return ((ResultSuccess) e.getResult()).getTargetScript();

View file

@ -1,7 +1,7 @@
import com.fasterxml.jackson.databind.Module;
import io.xpipe.app.beacon.impl.*;
import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.core.AppLogs; import io.xpipe.app.core.AppLogs;
import io.xpipe.app.exchange.*;
import io.xpipe.app.exchange.cli.*;
import io.xpipe.app.ext.*; import io.xpipe.app.ext.*;
import io.xpipe.app.issue.EventHandler; import io.xpipe.app.issue.EventHandler;
import io.xpipe.app.issue.EventHandlerImpl; import io.xpipe.app.issue.EventHandlerImpl;
@ -10,15 +10,15 @@ import io.xpipe.app.util.AppJacksonModule;
import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ProxyManagerProviderImpl; import io.xpipe.app.util.ProxyManagerProviderImpl;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.DataStateProvider; import io.xpipe.core.util.DataStateProvider;
import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.ModuleLayerLoader;
import io.xpipe.core.util.ProxyFunction; import io.xpipe.core.util.ProxyFunction;
import io.xpipe.core.util.ProxyManagerProvider; import io.xpipe.core.util.ProxyManagerProvider;
import com.fasterxml.jackson.databind.Module;
import org.slf4j.spi.SLF4JServiceProvider; import org.slf4j.spi.SLF4JServiceProvider;
open module io.xpipe.app { open module io.xpipe.app {
exports io.xpipe.app.beacon;
exports io.xpipe.app.core; exports io.xpipe.app.core;
exports io.xpipe.app.util; exports io.xpipe.app.util;
exports io.xpipe.app; exports io.xpipe.app;
@ -52,6 +52,7 @@ open module io.xpipe.app {
requires com.vladsch.flexmark; requires com.vladsch.flexmark;
requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.annotation;
requires net.synedra.validatorfx; requires net.synedra.validatorfx;
requires org.kordamp.ikonli.feather; requires org.kordamp.ikonli.feather;
requires io.xpipe.modulefs; requires io.xpipe.modulefs;
@ -79,14 +80,6 @@ open module io.xpipe.app {
requires net.steppschuh.markdowngenerator; requires net.steppschuh.markdowngenerator;
requires com.shinyhut.vernacular; requires com.shinyhut.vernacular;
// Required by extensions
requires java.security.jgss;
requires java.security.sasl;
requires java.xml;
requires java.xml.crypto;
requires java.sql;
requires java.sql.rowset;
// Required runtime modules // Required runtime modules
requires jdk.charsets; requires jdk.charsets;
requires jdk.crypto.cryptoki; requires jdk.crypto.cryptoki;
@ -100,8 +93,8 @@ open module io.xpipe.app {
// For debugging // For debugging
requires jdk.jdwp.agent; requires jdk.jdwp.agent;
requires org.kordamp.ikonli.core; requires org.kordamp.ikonli.core;
requires jdk.httpserver;
uses MessageExchangeImpl;
uses TerminalLauncher; uses TerminalLauncher;
uses io.xpipe.app.ext.ActionProvider; uses io.xpipe.app.ext.ActionProvider;
uses EventHandler; uses EventHandler;
@ -113,11 +106,11 @@ open module io.xpipe.app {
uses BrowserAction; uses BrowserAction;
uses LicenseProvider; uses LicenseProvider;
uses io.xpipe.app.util.LicensedFeature; uses io.xpipe.app.util.LicensedFeature;
uses io.xpipe.beacon.BeaconInterface;
provides Module with provides Module with
AppJacksonModule; AppJacksonModule;
provides ModuleLayerLoader with provides ModuleLayerLoader with
MessageExchangeImpls.Loader,
DataStoreProviders.Loader, DataStoreProviders.Loader,
ActionProvider.Loader, ActionProvider.Loader,
PrefsProvider.Loader, PrefsProvider.Loader,
@ -132,26 +125,15 @@ open module io.xpipe.app {
AppLogs.Slf4jProvider; AppLogs.Slf4jProvider;
provides EventHandler with provides EventHandler with
EventHandlerImpl; EventHandlerImpl;
provides MessageExchangeImpl with provides BeaconInterface with
ReadDrainExchangeImpl,
EditStoreExchangeImpl,
StoreProviderListExchangeImpl,
OpenExchangeImpl, OpenExchangeImpl,
LaunchExchangeImpl,
FocusExchangeImpl, FocusExchangeImpl,
StatusExchangeImpl, StatusExchangeImpl,
DrainExchangeImpl,
SinkExchangeImpl,
StopExchangeImpl, StopExchangeImpl,
HandshakeExchangeImpl,
ModeExchangeImpl, ModeExchangeImpl,
DialogExchangeImpl,
RemoveStoreExchangeImpl,
RenameStoreExchangeImpl,
ListStoresExchangeImpl,
StoreAddExchangeImpl,
AskpassExchangeImpl, AskpassExchangeImpl,
TerminalWaitExchangeImpl, TerminalWaitExchangeImpl,
TerminalLaunchExchangeImpl, TerminalLaunchExchangeImpl,
QueryStoreExchangeImpl,
VersionExchangeImpl; VersionExchangeImpl;
} }

View file

@ -1,314 +1,124 @@
package io.xpipe.beacon; package io.xpipe.beacon;
import io.xpipe.beacon.exchange.MessageExchanges; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.beacon.exchange.data.ClientErrorMessage; import io.xpipe.beacon.api.HandshakeExchange;
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
import io.xpipe.core.util.Deobfuscator;
import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.JacksonMapper;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.URI;
import java.io.OutputStream; import java.net.http.HttpClient;
import java.io.StringWriter; import java.net.http.HttpRequest;
import java.net.InetAddress; import java.net.http.HttpResponse;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR; public class BeaconClient {
public class BeaconClient implements AutoCloseable { private final int port;
private String token;
@Getter public BeaconClient(int port) {this.port = port;}
private final AutoCloseable base;
private final InputStream in; public static BeaconClient establishConnection(int port, BeaconClientInformation information) throws Exception {
private final OutputStream out; var client = new BeaconClient(port);
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder().client(information).build());
private BeaconClient(AutoCloseable base, InputStream in, OutputStream out) { client.token = response.getToken();
this.base = base;
this.in = in;
this.out = out;
}
public static BeaconClient establishConnection(ClientInformation information) throws Exception {
var socket = new Socket();
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort()), 5000);
socket.setSoTimeout(5000);
var client = new BeaconClient(socket, socket.getInputStream(), socket.getOutputStream());
client.sendObject(JacksonMapper.getDefault().valueToTree(information));
var res = client.receiveObject();
if (!res.isTextual() || !"ACK".equals(res.asText())) {
throw new BeaconException("Daemon responded with invalid acknowledgement");
}
socket.setSoTimeout(0);
return client; return client;
} }
public static Optional<BeaconClient> tryEstablishConnection(ClientInformation information) { public static Optional<BeaconClient> tryEstablishConnection(int port, BeaconClientInformation information) {
try { try {
return Optional.of(establishConnection(information)); return Optional.of(establishConnection(port, information));
} catch (Exception ex) { } catch (Exception ex) {
return Optional.empty(); return Optional.empty();
} }
} }
public void close() throws ConnectorException {
try {
base.close();
} catch (Exception ex) {
throw new ConnectorException("Couldn't close client", ex);
}
}
public InputStream receiveBody() throws ConnectorException { @SuppressWarnings("unchecked")
try { public <RES> RES performRequest(BeaconInterface<?> prov, String rawNode) throws
var sep = in.readNBytes(BODY_SEPARATOR.length); BeaconConnectorException, BeaconClientException, BeaconServerException {
if (sep.length != 0 && !Arrays.equals(BODY_SEPARATOR, sep)) { var content = rawNode;
throw new ConnectorException("Invalid body separator");
}
return BeaconFormat.readBlocks(in);
} catch (IOException ex) {
throw new ConnectorException(ex);
}
}
public OutputStream sendBody() throws ConnectorException {
try {
out.write(BODY_SEPARATOR);
return BeaconFormat.writeBlocks(out);
} catch (IOException ex) {
throw new ConnectorException(ex);
}
}
public <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
ObjectNode json = JacksonMapper.getDefault().valueToTree(req);
var prov = MessageExchanges.byRequest(req);
if (prov.isEmpty()) {
throw new ClientException("Unknown request class " + req.getClass());
}
json.set("messageType", new TextNode(prov.get().getId()));
json.set("messagePhase", new TextNode("request"));
// json.set("id", new TextNode(UUID.randomUUID().toString()));
var msg = JsonNodeFactory.instance.objectNode();
msg.set("xPipeMessage", json);
if (BeaconConfig.printMessages()) {
System.out.println(
"Sending request to server of type " + req.getClass().getName());
}
sendObject(msg);
}
public void sendEOF() throws ConnectorException {
try (OutputStream ignored = BeaconFormat.writeBlocks(out)) {
} catch (IOException ex) {
throw new ConnectorException("Couldn't write to socket", ex);
}
}
public void sendObject(JsonNode node) throws ConnectorException {
var writer = new StringWriter();
var mapper = JacksonMapper.getDefault();
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
g.writeTree(node);
} catch (IOException ex) {
throw new ConnectorException("Couldn't serialize request", ex);
}
var content = writer.toString();
if (BeaconConfig.printMessages()) { if (BeaconConfig.printMessages()) {
System.out.println("Sending raw request:"); System.out.println("Sending raw request:");
System.out.println(content); System.out.println(content);
} }
try (OutputStream blockOut = BeaconFormat.writeBlocks(out)) { var client = HttpClient.newHttpClient();
blockOut.write(content.getBytes(StandardCharsets.UTF_8)); HttpResponse<String> response;
} catch (IOException ex) { try {
throw new ConnectorException("Couldn't write to socket", ex); var uri = URI.create("http://localhost:" + port + prov.getPath());
} var builder = HttpRequest.newBuilder();
} if (token != null) {
builder.header("Authorization", "Bearer " + token);
public <T extends ResponseMessage> T receiveResponse() throws ConnectorException, ClientException, ServerException { }
return parseResponse(receiveObject()); var httpRequest = builder
} .uri(uri).POST(HttpRequest.BodyPublishers.ofString(content)).build();
response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
private JsonNode receiveObject() throws ConnectorException, ClientException, ServerException { } catch (Exception ex) {
JsonNode node; throw new BeaconConnectorException("Couldn't send request", ex);
try (InputStream blockIn = BeaconFormat.readBlocks(in)) {
node = JacksonMapper.getDefault().readTree(blockIn);
} catch (IOException ex) {
throw new ConnectorException("Couldn't read from socket", ex);
} }
if (BeaconConfig.printMessages()) { if (BeaconConfig.printMessages()) {
System.out.println("Received response:"); System.out.println("Received raw response:");
System.out.println(node.toPrettyString()); System.out.println(response.body());
} }
if (node.isMissingNode()) { var se = parseServerError(response);
throw new ConnectorException("Received unexpected EOF");
}
var se = parseServerError(node);
if (se.isPresent()) { if (se.isPresent()) {
se.get().throwError(); se.get().throwError();
} }
var ce = parseClientError(node); var ce = parseClientError(response);
if (ce.isPresent()) { if (ce.isPresent()) {
throw ce.get().throwException(); throw ce.get().throwException();
} }
return node;
}
private Optional<ClientErrorMessage> parseClientError(JsonNode node) throws ConnectorException {
ObjectNode content = (ObjectNode) node.get("xPipeClientError");
if (content == null) {
return Optional.empty();
}
try { try {
var message = JacksonMapper.getDefault().treeToValue(content, ClientErrorMessage.class); var reader = JacksonMapper.getDefault().readerFor(prov.getResponseClass());
return Optional.of(message); var v = (RES) reader.readValue(response.body());
return v;
} catch (IOException ex) { } catch (IOException ex) {
throw new ConnectorException("Couldn't parse client error message", ex); throw new BeaconConnectorException("Couldn't parse response", ex);
} }
} }
private Optional<ServerErrorMessage> parseServerError(JsonNode node) throws ConnectorException { public <REQ, RES> RES performRequest(REQ req) throws BeaconConnectorException, BeaconClientException, BeaconServerException {
ObjectNode content = (ObjectNode) node.get("xPipeServerError"); ObjectNode node = JacksonMapper.getDefault().valueToTree(req);
if (content == null) { var prov = BeaconInterface.byRequest(req);
return Optional.empty();
}
try {
var message = JacksonMapper.getDefault().treeToValue(content, ServerErrorMessage.class);
Deobfuscator.deobfuscate(message.getError());
return Optional.of(message);
} catch (IOException ex) {
throw new ConnectorException("Couldn't parse server error message", ex);
}
}
private <T extends ResponseMessage> T parseResponse(JsonNode header) throws ConnectorException {
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
var type = content.required("messageType").textValue();
var phase = content.required("messagePhase").textValue();
// var requestId = UUID.fromString(content.required("id").textValue());
if (!phase.equals("response")) {
throw new IllegalArgumentException();
}
content.remove("messageType");
content.remove("messagePhase");
// content.remove("id");
var prov = MessageExchanges.byId(type);
if (prov.isEmpty()) { if (prov.isEmpty()) {
throw new IllegalArgumentException("Unknown response id " + type); throw new IllegalArgumentException("Unknown request class " + req.getClass());
}
if (BeaconConfig.printMessages()) {
System.out.println("Sending request to server of type " + req.getClass().getName());
}
return performRequest(prov.get(), node.toPrettyString());
}
private Optional<BeaconClientErrorResponse> parseClientError(HttpResponse<String> response) throws BeaconConnectorException {
if (response.statusCode() < 400 || response.statusCode() > 499) {
return Optional.empty();
} }
try { try {
var reader = JacksonMapper.getDefault().readerFor(prov.get().getResponseClass()); var v = JacksonMapper.getDefault().readValue(response.body(), BeaconClientErrorResponse.class);
return reader.readValue(content); return Optional.of(v);
} catch (IOException ex) { } catch (IOException ex) {
throw new ConnectorException("Couldn't parse response", ex); throw new BeaconConnectorException("Couldn't parse client error message", ex);
} }
} }
public InputStream getRawInputStream() { private Optional<BeaconServerErrorResponse> parseServerError(HttpResponse<String> response) throws BeaconConnectorException {
return in; if (response.statusCode() < 500 || response.statusCode() > 599) {
} return Optional.empty();
public OutputStream getRawOutputStream() {
return out;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public abstract static class ClientInformation {
public final CliClientInformation cli() {
return (CliClientInformation) this;
} }
public abstract String toDisplayString(); try {
} var v = JacksonMapper.getDefault().readValue(response.body(), BeaconServerErrorResponse.class);
return Optional.of(v);
@JsonTypeName("cli") } catch (IOException ex) {
@Value throw new BeaconConnectorException("Couldn't parse client error message", ex);
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class CliClientInformation extends ClientInformation {
@Override
public String toDisplayString() {
return "XPipe CLI";
} }
} }
@JsonTypeName("daemon")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class DaemonInformation extends ClientInformation {
@Override
public String toDisplayString() {
return "Daemon";
}
}
@JsonTypeName("gateway")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class GatewayClientInformation extends ClientInformation {
String version;
@Override
public String toDisplayString() {
return "XPipe Gateway " + version;
}
}
@JsonTypeName("api")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class ApiClientInformation extends ClientInformation {
String version;
String language;
@Override
public String toDisplayString() {
return String.format("XPipe %s API v%s", language, version);
}
}
} }

View file

@ -1,6 +1,4 @@
package io.xpipe.beacon.exchange.data; package io.xpipe.beacon;
import io.xpipe.beacon.ClientException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -12,11 +10,11 @@ import lombok.extern.jackson.Jacksonized;
@Builder @Builder
@Jacksonized @Jacksonized
@AllArgsConstructor @AllArgsConstructor
public class ClientErrorMessage { public class BeaconClientErrorResponse {
String message; String message;
public ClientException throwException() { public BeaconClientException throwException() {
return new ClientException(message); return new BeaconClientException(message);
} }
} }

View file

@ -0,0 +1,23 @@
package io.xpipe.beacon;
/**
* Indicates that a client request was invalid.
*/
public class BeaconClientException extends Exception {
public BeaconClientException(String message) {
super(message);
}
public BeaconClientException(String message, Throwable cause) {
super(message, cause);
}
public BeaconClientException(Throwable cause) {
super(cause);
}
public BeaconClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,57 @@
package io.xpipe.beacon;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
public abstract class BeaconClientInformation {
public abstract String toDisplayString();
@JsonTypeName("cli")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class CliClientInformation extends BeaconClientInformation {
@Override
public String toDisplayString() {
return "XPipe CLI";
}
}
@JsonTypeName("daemon")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class DaemonInformation extends BeaconClientInformation {
@Override
public String toDisplayString() {
return "Daemon";
}
}
@JsonTypeName("api")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class ApiClientInformation extends BeaconClientInformation {
String name;
@Override
public String toDisplayString() {
return name;
}
}
}

View file

@ -1,15 +1,11 @@
package io.xpipe.beacon; package io.xpipe.beacon;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.nio.charset.StandardCharsets;
@UtilityClass @UtilityClass
public class BeaconConfig { public class BeaconConfig {
public static final byte[] BODY_SEPARATOR = "\n\n".getBytes(StandardCharsets.UTF_8);
public static final String BEACON_PORT_PROP = "io.xpipe.beacon.port"; public static final String BEACON_PORT_PROP = "io.xpipe.beacon.port";
public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs"; public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs";
private static final String PRINT_MESSAGES_PROPERTY = "io.xpipe.beacon.printMessages"; private static final String PRINT_MESSAGES_PROPERTY = "io.xpipe.beacon.printMessages";
@ -17,14 +13,6 @@ public class BeaconConfig {
private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon"; private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon";
private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput"; private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand"; private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand";
private static final String LOCAL_PROXY_PROP = "io.xpipe.beacon.localProxy";
public static boolean localProxy() {
if (System.getProperty(LOCAL_PROXY_PROP) != null) {
return Boolean.parseBoolean(System.getProperty(LOCAL_PROXY_PROP));
}
return false;
}
public static boolean printMessages() { public static boolean printMessages() {
if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) { if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) {

View file

@ -1,189 +0,0 @@
package io.xpipe.beacon;
import io.xpipe.core.util.FailableBiConsumer;
import io.xpipe.core.util.FailableConsumer;
import lombok.Getter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public abstract class BeaconConnection implements AutoCloseable {
@Getter
protected BeaconClient beaconClient;
private InputStream bodyInput;
private OutputStream bodyOutput;
protected abstract void constructSocket();
@Override
public void close() {
try {
if (beaconClient != null) {
beaconClient.close();
}
beaconClient = null;
} catch (Exception e) {
beaconClient = null;
throw new BeaconException("Could not close beacon connection", e);
}
}
public void withOutputStream(FailableConsumer<OutputStream, IOException> ex) {
try {
ex.accept(getOutputStream());
} catch (IOException e) {
throw new BeaconException("Could not write to beacon output stream", e);
}
}
public void withInputStream(FailableConsumer<InputStream, IOException> ex) {
try {
ex.accept(getInputStream());
} catch (IOException e) {
throw new BeaconException("Could not read from beacon output stream", e);
}
}
public void checkClosed() {
if (beaconClient == null) {
throw new BeaconException("Socket is closed");
}
}
public OutputStream getOutputStream() {
checkClosed();
if (bodyOutput == null) {
throw new IllegalStateException("Body output has not started yet");
}
return bodyOutput;
}
public InputStream getInputStream() {
checkClosed();
if (bodyInput == null) {
throw new IllegalStateException("Body input has not started yet");
}
return bodyInput;
}
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
REQ req, FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed();
performInputOutputExchange(req, null, responseConsumer);
}
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputOutputExchange(
REQ req,
FailableConsumer<OutputStream, IOException> reqWriter,
FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed();
try {
beaconClient.sendRequest(req);
if (reqWriter != null) {
try (var out = sendBody()) {
reqWriter.accept(out);
}
}
RES res = beaconClient.receiveResponse();
try (var in = receiveBody()) {
responseConsumer.accept(res, in);
}
} catch (Exception e) {
throw unwrapException(e);
}
}
public <REQ extends RequestMessage> void sendRequest(REQ req) {
checkClosed();
try {
beaconClient.sendRequest(req);
} catch (Exception e) {
throw unwrapException(e);
}
}
public <RES extends ResponseMessage> RES receiveResponse() {
checkClosed();
try {
return beaconClient.receiveResponse();
} catch (Exception e) {
throw unwrapException(e);
}
}
public OutputStream sendBody() {
checkClosed();
try {
bodyOutput = beaconClient.sendBody();
return bodyOutput;
} catch (Exception e) {
throw unwrapException(e);
}
}
public InputStream receiveBody() {
checkClosed();
try {
bodyInput = beaconClient.receiveBody();
return bodyInput;
} catch (Exception e) {
throw unwrapException(e);
}
}
public <REQ extends RequestMessage, RES extends ResponseMessage> RES performOutputExchange(
REQ req, FailableConsumer<OutputStream, Exception> reqWriter) {
checkClosed();
try {
beaconClient.sendRequest(req);
try (var out = sendBody()) {
reqWriter.accept(out);
}
return beaconClient.receiveResponse();
} catch (Exception e) {
throw unwrapException(e);
}
}
public <REQ extends RequestMessage, RES extends ResponseMessage> RES performSimpleExchange(REQ req) {
checkClosed();
try {
beaconClient.sendRequest(req);
return beaconClient.receiveResponse();
} catch (Exception e) {
throw unwrapException(e);
}
}
private BeaconException unwrapException(Exception exception) {
if (exception instanceof ServerException s) {
return new BeaconException("An internal server error occurred", s);
}
if (exception instanceof ClientException s) {
return new BeaconException("A client error occurred", s);
}
if (exception instanceof ConnectorException s) {
return new BeaconException("A beacon connection error occurred", s);
}
return new BeaconException("An unexpected error occurred", exception);
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.beacon;
/**
* Indicates that a connection error occurred.
*/
public class BeaconConnectorException extends Exception {
public BeaconConnectorException() {}
public BeaconConnectorException(String message) {
super(message);
}
public BeaconConnectorException(String message, Throwable cause) {
super(message, cause);
}
public BeaconConnectorException(Throwable cause) {
super(cause);
}
public BeaconConnectorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,25 +0,0 @@
package io.xpipe.beacon;
/**
* An unchecked exception that will be thrown in any case of an underlying exception.
*/
public class BeaconException extends RuntimeException {
public BeaconException() {}
public BeaconException(String message) {
super(message);
}
public BeaconException(String message, Throwable cause) {
super(message, cause);
}
public BeaconException(Throwable cause) {
super(cause);
}
public BeaconException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,112 +0,0 @@
package io.xpipe.beacon;
import lombok.experimental.UtilityClass;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
@UtilityClass
public class BeaconFormat {
private static final int SEGMENT_SIZE = 65536;
public static OutputStream writeBlocks(OutputStream out) {
return new OutputStream() {
private final byte[] currentBytes = new byte[SEGMENT_SIZE];
private int index;
@Override
public void write(int b) throws IOException {
if (isClosed()) {
throw new IllegalStateException("Output is closed");
}
if (index == currentBytes.length) {
finishBlock();
}
currentBytes[index] = (byte) b;
index++;
}
@Override
public void close() throws IOException {
if (isClosed()) {
return;
}
finishBlock();
out.flush();
index = -1;
}
private boolean isClosed() {
return index == -1;
}
private void finishBlock() throws IOException {
if (isClosed()) {
throw new IllegalStateException("Output is closed");
}
if (BeaconConfig.printMessages()) {
System.out.println("Sending data block of length " + index);
}
int length = index;
var lengthBuffer = ByteBuffer.allocate(4).putInt(length);
out.write(lengthBuffer.array());
out.write(currentBytes, 0, length);
index = 0;
}
};
}
public static InputStream readBlocks(InputStream in) {
return new InputStream() {
private byte[] currentBytes;
private int index;
private boolean lastBlock;
@Override
public int read() throws IOException {
if ((currentBytes == null || index == currentBytes.length) && !lastBlock) {
if (!readBlock()) {
return -1;
}
}
if (currentBytes != null && index == currentBytes.length && lastBlock) {
return -1;
}
int out = currentBytes[index] & 0xff;
index++;
return out;
}
private boolean readBlock() throws IOException {
var length = in.readNBytes(4);
if (length.length < 4) {
return false;
}
var lengthInt = ByteBuffer.wrap(length).getInt();
if (BeaconConfig.printMessages()) {
System.out.println("Receiving data block of length " + lengthInt);
}
currentBytes = in.readNBytes(lengthInt);
index = 0;
if (lengthInt < SEGMENT_SIZE) {
lastBlock = true;
}
return true;
}
};
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon;
import io.xpipe.core.util.FailableRunnable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* An exchange handler responsible for properly handling a request and sending a response.
*/
public interface BeaconHandler {
/**
* Execute a Runnable after the initial response has been sent.
*
* @param r the runnable to execute
*/
void postResponse(FailableRunnable<Exception> r);
/**
* Prepares to attach a body to a response.
*
* @return the output stream that can be used to write the body payload
*/
OutputStream sendBody() throws IOException;
/**
* Prepares to read an attached body of a request.
*
* @return the input stream that can be used to read the body payload
*/
InputStream receiveBody() throws IOException;
}

View file

@ -0,0 +1,74 @@
package io.xpipe.beacon;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.core.util.ModuleLayerLoader;
import lombok.SneakyThrows;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public abstract class BeaconInterface<T> {
private static List<BeaconInterface<?>> ALL;
public static List<BeaconInterface<?>> getAll() {
return ALL;
}
public static Optional<BeaconInterface<?>> byPath(String path) {
return ALL.stream()
.filter(d -> d.getPath().equals(path))
.findAny();
}
public static <RQ> Optional<BeaconInterface<?>> byRequest(RQ req) {
return ALL.stream()
.filter(d -> d.getRequestClass().equals(req.getClass()))
.findAny();
}
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
var services = layer != null ? ServiceLoader.load(layer, BeaconInterface.class) : ServiceLoader.load(BeaconInterface.class);
ALL = services.stream()
.map(ServiceLoader.Provider::get)
.map(beaconInterface -> (BeaconInterface<?>) beaconInterface)
.collect(Collectors.toList());
// Remove parent classes
ALL.removeIf(beaconInterface -> ALL.stream().anyMatch(other ->
!other.equals(beaconInterface) && beaconInterface.getClass().isAssignableFrom(other.getClass())));
}
}
@SuppressWarnings("unchecked")
@SneakyThrows
public Class<T> getRequestClass() {
var c = getClass().getSuperclass();
var name = (c.getSuperclass().equals(BeaconInterface.class) ? c : getClass()).getName() + "$Request";
return (Class<T>) Class.forName(name);
}
@SuppressWarnings("unchecked")
@SneakyThrows
public Class<T> getResponseClass() {
var c = getClass().getSuperclass();
var name = (c.getSuperclass().equals(BeaconInterface.class) ? c : getClass()).getName() + "$Response";
return (Class<T>) Class.forName(name);
}
public boolean requiresAuthentication() {
return true;
}
public abstract String getPath();
public Object handle(HttpExchange exchange, T body) throws IOException, BeaconClientException, BeaconServerException {
throw new UnsupportedOperationException();
}
}

View file

@ -8,8 +8,8 @@ public class BeaconJacksonModule extends SimpleModule {
@Override @Override
public void setupModule(SetupContext context) { public void setupModule(SetupContext context) {
context.registerSubtypes( context.registerSubtypes(
new NamedType(BeaconClient.ApiClientInformation.class), new NamedType(BeaconClientInformation.ApiClientInformation.class),
new NamedType(BeaconClient.CliClientInformation.class), new NamedType(BeaconClientInformation.CliClientInformation.class),
new NamedType(BeaconClient.DaemonInformation.class)); new NamedType(BeaconClientInformation.DaemonInformation.class));
} }
} }

View file

@ -1,6 +1,6 @@
package io.xpipe.beacon; package io.xpipe.beacon;
import io.xpipe.beacon.exchange.StopExchange; import io.xpipe.beacon.api.StopExchange;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.util.XPipeDaemonMode; import io.xpipe.core.util.XPipeDaemonMode;
@ -18,9 +18,9 @@ import java.util.List;
*/ */
public class BeaconServer { public class BeaconServer {
public static boolean isReachable() { public static boolean isReachable(int port) {
try (var socket = new Socket()) { try (var socket = new Socket()) {
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort()), 5000); socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 5000);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@ -108,8 +108,7 @@ public class BeaconServer {
} }
public static boolean tryStop(BeaconClient client) throws Exception { public static boolean tryStop(BeaconClient client) throws Exception {
client.sendRequest(StopExchange.Request.builder().build()); StopExchange.Response res = client.performRequest(StopExchange.Request.builder().build());
StopExchange.Response res = client.receiveResponse();
return res.isSuccess(); return res.isSuccess();
} }

View file

@ -0,0 +1,20 @@
package io.xpipe.beacon;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@SuppressWarnings("ClassCanBeRecord")
@Value
@Builder
@Jacksonized
@AllArgsConstructor
public class BeaconServerErrorResponse {
Throwable error;
public void throwError() throws BeaconServerException {
throw new BeaconServerException(error.getMessage(), error);
}
}

View file

@ -0,0 +1,23 @@
package io.xpipe.beacon;
/**
* Indicates that an internal server error occurred.
*/
public class BeaconServerException extends Exception {
public BeaconServerException(String message) {
super(message);
}
public BeaconServerException(String message, Throwable cause) {
super(message, cause);
}
public BeaconServerException(Throwable cause) {
super(cause);
}
public BeaconServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,25 +0,0 @@
package io.xpipe.beacon;
/**
* Indicates that a client request caused an issue.
*/
public class ClientException extends Exception {
public ClientException() {}
public ClientException(String message) {
super(message);
}
public ClientException(String message, Throwable cause) {
super(message, cause);
}
public ClientException(Throwable cause) {
super(cause);
}
public ClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,25 +0,0 @@
package io.xpipe.beacon;
/**
* Indicates that a connection error occurred.
*/
public class ConnectorException extends Exception {
public ConnectorException() {}
public ConnectorException(String message) {
super(message);
}
public ConnectorException(String message, Throwable cause) {
super(message, cause);
}
public ConnectorException(Throwable cause) {
super(cause);
}
public ConnectorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,3 +0,0 @@
package io.xpipe.beacon;
public interface RequestMessage {}

View file

@ -1,3 +0,0 @@
package io.xpipe.beacon;
public interface ResponseMessage {}

View file

@ -1,25 +0,0 @@
package io.xpipe.beacon;
/**
* Indicates that an internal server error occurred.
*/
public class ServerException extends Exception {
public ServerException() {}
public ServerException(String message) {
super(message);
}
public ServerException(String message, Throwable cause) {
super(message, cause);
}
public ServerException(Throwable cause) {
super(cause);
}
public ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,9 +1,7 @@
package io.xpipe.beacon.exchange; package io.xpipe.beacon.api;
import io.xpipe.beacon.RequestMessage; import io.xpipe.beacon.BeaconInterface;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -11,17 +9,17 @@ import lombok.extern.jackson.Jacksonized;
import java.util.UUID; import java.util.UUID;
public class AskpassExchange implements MessageExchange { public class AskpassExchange extends BeaconInterface<AskpassExchange.Request> {
@Override @Override
public String getId() { public String getPath() {
return "askpass"; return "/askpass";
} }
@Jacksonized @Jacksonized
@Builder @Builder
@Value @Value
public static class Request implements RequestMessage { public static class Request {
UUID secretId; UUID secretId;
@NonNull @NonNull
@ -33,7 +31,7 @@ public class AskpassExchange implements MessageExchange {
@Jacksonized @Jacksonized
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response {
SecretValue value; SecretValue value;
} }
} }

View file

@ -0,0 +1,29 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.XPipeDaemonMode;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class FocusExchange extends BeaconInterface<FocusExchange.Request> {
@Override
public String getPath() {
return "/focus";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
XPipeDaemonMode mode;
}
@Jacksonized
@Builder
@Value
public static class Response {}
}

View file

@ -0,0 +1,34 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconClientInformation;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request> {
@Override
public String getPath() {
return "/handshake";
}
@Override
public boolean requiresAuthentication() {
return false;
}
@Jacksonized
@Builder
@Value
public static class Request {
BeaconClientInformation client;
}
@Jacksonized
@Builder
@Value
public static class Response {
String token;
}
}

View file

@ -0,0 +1,32 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.XPipeDaemonMode;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ModeExchange extends BeaconInterface<ModeExchange.Request> {
@Override
public String getPath() {
return "/mode";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
XPipeDaemonMode mode;
}
@Jacksonized
@Builder
@Value
public static class Response {
@NonNull
XPipeDaemonMode usedMode;
}
}

View file

@ -0,0 +1,30 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class OpenExchange extends BeaconInterface<OpenExchange.Request> {
@Override
public String getPath() {
return "/open";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
List<String> arguments;
}
@Jacksonized
@Builder
@Value
public static class Response {}
}

View file

@ -0,0 +1,27 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class StatusExchange extends BeaconInterface<StatusExchange.Request> {
@Override
public String getPath() {
return "/status";
}
@Value
@Jacksonized
@Builder
public static class Request {
}
@Jacksonized
@Builder
@Value
public static class Response {
String mode;
}
}

View file

@ -0,0 +1,30 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
/**
* Requests the daemon to stop.
*/
public class StopExchange extends BeaconInterface<StopExchange.Request> {
@Override
public String getPath() {
return "/stop";
}
@Jacksonized
@Builder
@Value
public static class Request {
}
@Jacksonized
@Builder
@Value
public static class Response {
boolean success;
}
}

View file

@ -1,8 +1,6 @@
package io.xpipe.beacon.exchange; package io.xpipe.beacon.api;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -11,17 +9,17 @@ import lombok.extern.jackson.Jacksonized;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.UUID; import java.util.UUID;
public class TerminalLaunchExchange implements MessageExchange { public class TerminalLaunchExchange extends BeaconInterface<TerminalLaunchExchange.Request> {
@Override @Override
public String getId() { public String getPath() {
return "terminalLaunch"; return "/terminalLaunch";
} }
@Jacksonized @Jacksonized
@Builder @Builder
@Value @Value
public static class Request implements RequestMessage { public static class Request {
@NonNull @NonNull
UUID request; UUID request;
} }
@ -29,7 +27,7 @@ public class TerminalLaunchExchange implements MessageExchange {
@Jacksonized @Jacksonized
@Builder @Builder
@Value @Value
public static class Response implements ResponseMessage { public static class Response {
@NonNull @NonNull
Path targetFile; Path targetFile;
} }

View file

@ -0,0 +1,30 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
public class TerminalWaitExchange extends BeaconInterface<TerminalWaitExchange.Request> {
@Override
public String getPath() {
return "/terminalWait";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
UUID request;
}
@Jacksonized
@Builder
@Value
public static class Response {}
}

View file

@ -0,0 +1,30 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class VersionExchange extends BeaconInterface<VersionExchange.Request> {
@Override
public String getPath() {
return "/version";
}
@Jacksonized
@Builder
@Value
public static class Request {
}
@Jacksonized
@Builder
@Value
public static class Response {
String version;
String buildVersion;
String jvmVersion;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.store.DataStoreId;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class DrainExchange implements MessageExchange {
@Override
public String getId() {
return "drain";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
DataStoreId source;
@NonNull
String path;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.util.XPipeDaemonMode;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class FocusExchange implements MessageExchange {
@Override
public String getId() {
return "focus";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
XPipeDaemonMode mode;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,36 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.store.DataStoreId;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class LaunchExchange implements MessageExchange {
@Override
public String getId() {
return "launch";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
DataStoreId id;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
@NonNull
List<String> command;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange;
import lombok.SneakyThrows;
/**
* A message exchange scheme that implements a certain functionality.
*/
public interface MessageExchange {
/**
* The unique id of this exchange that will be included in the messages.
*/
String getId();
/**
* Returns the request class, needed for serialization.
*/
@SneakyThrows
default Class<?> getRequestClass() {
var c = getClass().getSuperclass();
var name = (MessageExchange.class.isAssignableFrom(c) ? c : getClass()).getName() + "$Request";
return Class.forName(name);
}
/**
* Returns the response class, needed for serialization.
*/
@SneakyThrows
default Class<?> getResponseClass() {
var c = getClass().getSuperclass();
var name = (MessageExchange.class.isAssignableFrom(c) ? c : getClass()).getName() + "$Response";
return Class.forName(name);
}
}

View file

@ -1,48 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public class MessageExchanges {
private static List<MessageExchange> ALL;
public static void loadAll() {
if (ALL == null) {
ALL = ServiceLoader.load(MessageExchange.class).stream()
.map(s -> {
return s.get();
})
.collect(Collectors.toList());
}
}
public static Optional<MessageExchange> byId(String name) {
loadAll();
return ALL.stream().filter(d -> d.getId().equals(name)).findAny();
}
public static <RQ extends RequestMessage> Optional<MessageExchange> byRequest(RQ req) {
loadAll();
return ALL.stream()
.filter(d -> d.getRequestClass().equals(req.getClass()))
.findAny();
}
public static <RP extends ResponseMessage> Optional<MessageExchange> byResponse(RP rep) {
loadAll();
return ALL.stream()
.filter(d -> d.getResponseClass().equals(rep.getClass()))
.findAny();
}
public static List<MessageExchange> getAll() {
loadAll();
return ALL;
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class OpenExchange implements MessageExchange {
@Override
public String getId() {
return "open";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
List<String> arguments;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,51 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.store.DataStore;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.LinkedHashMap;
/**
* Queries general information about a data source.
*/
public class QueryStoreExchange implements MessageExchange {
@Override
public String getId() {
return "queryStore";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String name;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
@NonNull
String name;
String information;
String summary;
@NonNull
String provider;
@NonNull
LinkedHashMap<String, String> config;
DataStore internalStore;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.store.DataStoreId;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class SinkExchange implements MessageExchange {
@Override
public String getId() {
return "sink";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
DataStoreId source;
@NonNull
String path;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
/**
* Requests the daemon to stop.
*/
public class StopExchange implements MessageExchange {
@Override
public String getId() {
return "stop";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
boolean success;
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
public class TerminalWaitExchange implements MessageExchange {
@Override
public String getId() {
return "terminalWait";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
UUID request;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,50 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.DialogElement;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
public class DialogExchange implements MessageExchange {
@Override
public String getId() {
return "dialog";
}
@Override
public Class<DialogExchange.Request> getRequestClass() {
return DialogExchange.Request.class;
}
@Override
public Class<DialogExchange.Response> getResponseClass() {
return DialogExchange.Response.class;
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
UUID dialogKey;
String value;
boolean cancel;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
DialogElement element;
String errorMsg;
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.DialogReference;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class EditStoreExchange implements MessageExchange {
@Override
public String getId() {
return "editEntry";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String name;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
@NonNull
DialogReference dialog;
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.exchange.data.CollectionListEntry;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class ListCollectionsExchange implements MessageExchange {
@Override
public String getId() {
return "listCollections";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
List<CollectionListEntry> entries;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.exchange.data.EntryListEntry;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class ListEntriesExchange implements MessageExchange {
@Override
public String getId() {
return "listEntries";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
String collection;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
List<EntryListEntry> entries;
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.exchange.data.StoreListEntry;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class ListStoresExchange implements MessageExchange {
@Override
public String getId() {
return "listStores";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
List<StoreListEntry> entries;
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.util.XPipeDaemonMode;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ModeExchange implements MessageExchange {
@Override
public String getId() {
return "mode";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
XPipeDaemonMode mode;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
@NonNull
XPipeDaemonMode usedMode;
}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ReadDrainExchange implements MessageExchange {
@Override
public String getId() {
return "readDrain";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String name;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RemoveCollectionExchange implements MessageExchange {
@Override
public String getId() {
return "removeCollection";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String collectionName;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RemoveStoreExchange implements MessageExchange {
@Override
public String getId() {
return "removeStore";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String storeName;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RenameCollectionExchange implements MessageExchange {
@Override
public String getId() {
return "renameCollection";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String collectionName;
@NonNull
String newName;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RenameStoreExchange implements MessageExchange {
@Override
public String getId() {
return "renameStore";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String storeName;
@NonNull
String newName;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {}
}

View file

@ -1,29 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class StatusExchange implements MessageExchange {
@Override
public String getId() {
return "status";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
String mode;
}
}

View file

@ -1,36 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.DialogReference;
import io.xpipe.core.store.DataStore;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class StoreAddExchange implements MessageExchange {
@Override
public String getId() {
return "storeAdd";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
DataStore storeInput;
String type;
String name;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
DialogReference config;
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.exchange.data.ProviderEntry;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.Map;
public class StoreProviderListExchange implements MessageExchange {
@Override
public String getId() {
return "storeProviderList";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
@NonNull
Map<String, List<ProviderEntry>> entries;
}
}

View file

@ -1,32 +0,0 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
import io.xpipe.beacon.exchange.MessageExchange;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class VersionExchange implements MessageExchange {
@Override
public String getId() {
return "version";
}
@lombok.extern.jackson.Jacksonized
@lombok.Builder
@lombok.Value
public static class Request implements RequestMessage {}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
String version;
String buildVersion;
String jvmVersion;
}
}

Some files were not shown because too many files have changed in this diff Show more