This commit is contained in:
crschnick 2024-05-14 15:40:45 +00:00
parent 6dd0e6d255
commit b68ada9e78
120 changed files with 1084 additions and 3160 deletions

View file

@ -45,9 +45,7 @@ dependencies {
api 'io.sentry:sentry:7.8.0'
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.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-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-materialdesign2-pack', version: "12.2.0"
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
@ -86,7 +84,6 @@ run {
systemProperty 'io.xpipe.app.fullVersion', rootProject.fullVersion
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

View file

@ -0,0 +1,90 @@
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.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;
private final int port;
private boolean running;
private HttpServer server;
@Getter
private Set<BeaconSession> sessions = new HashSet<>();
private AppBeaconServer(int port) {
this.port = port;
}
public static void init() {
int port = -1;
try {
port = BeaconConfig.getUsedPort();
INSTANCE = new AppBeaconServer(port);
INSTANCE.start();
TrackEvent.withInfo("Initialized http server")
.tag("port", port)
.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 " + port)
.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.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.AskpassExchange;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.AskpassExchange;
public class AskpassExchangeImpl extends AskpassExchange
implements MessageExchangeImpl<AskpassExchange.Request, AskpassExchange.Response> {
import java.io.IOException;
public class AskpassExchangeImpl extends AskpassExchange {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var found = msg.getSecretId() != null
? SecretManager.getProgress(msg.getRequest(), msg.getSecretId())
: 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.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.VersionExchange;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.VersionExchange;
public class VersionExchangeImpl extends VersionExchange
implements MessageExchangeImpl<VersionExchange.Request, VersionExchange.Response> {
import java.io.IOException;
public class VersionExchangeImpl extends VersionExchange {
@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") + " "
+ System.getProperty("java.vm.name") + " ("
+ System.getProperty("java.vm.version") + ")";

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

View file

@ -109,6 +109,7 @@ public abstract class OperationMode {
AppProperties.logArguments(args);
AppProperties.logSystemProperties();
AppProperties.logPassedProperties();
AppExtensionManager.init(true);
TrackEvent.info("Finished initial setup");
} catch (Throwable ex) {
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,24 +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.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());
}
}
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

@ -10,12 +10,11 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.beacon.BeaconServer;
import io.xpipe.beacon.exchange.FocusExchange;
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.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation;
import lombok.SneakyThrows;
import picocli.CommandLine;
@ -83,10 +82,8 @@ public class LauncherCommand implements Callable<Integer> {
try {
if (BeaconServer.isReachable()) {
try (var con = new LauncherConnection()) {
con.constructSocket();
con.performSimpleExchange(FocusExchange.Request.builder()
.mode(getEffectiveMode())
.build());
con.establishConnection();
con.performSimpleExchange(FocusExchange.Request.builder().mode(getEffectiveMode()).build());
if (!inputs.isEmpty()) {
con.performSimpleExchange(
OpenExchange.Request.builder().arguments(inputs).build());
@ -94,9 +91,11 @@ public class LauncherCommand implements Callable<Integer> {
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
con.performSimpleExchange(OpenExchange.Request.builder()
.arguments(List.of(e.getURI().toString()))
.build());
try {
con.performSimpleExchange(OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle();
}
});
ThreadHelper.sleep(1000);
}

View file

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

View file

@ -1,13 +1,12 @@
package io.xpipe.app.util;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.ServerException;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.core.process.ProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.TerminalInitScriptConfig;
import io.xpipe.core.process.WorkingDirectoryFunction;
import io.xpipe.core.store.FilePath;
import lombok.Setter;
import lombok.Value;
import lombok.experimental.NonFinal;
@ -73,10 +72,10 @@ public class TerminalLauncherManager {
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);
if (e == null) {
throw new ClientException("Unknown launch request " + request);
throw new BeaconClientException("Unknown launch request " + request);
}
while (true) {
@ -89,21 +88,21 @@ public class TerminalLauncherManager {
if (r instanceof ResultFailure failure) {
entries.remove(request);
var t = failure.getThrowable();
throw new ServerException(t);
throw new BeaconServerException(t);
}
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);
if (e == null) {
throw new ClientException("Unknown launch request " + request);
throw new BeaconClientException("Unknown launch request " + request);
}
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();

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.core.AppLogs;
import io.xpipe.app.exchange.*;
import io.xpipe.app.exchange.cli.*;
import io.xpipe.app.ext.*;
import io.xpipe.app.issue.EventHandler;
import io.xpipe.app.issue.EventHandlerImpl;
@ -10,12 +10,11 @@ import io.xpipe.app.util.AppJacksonModule;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ProxyManagerProviderImpl;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.DataStateProvider;
import io.xpipe.core.util.ModuleLayerLoader;
import io.xpipe.core.util.ProxyFunction;
import io.xpipe.core.util.ProxyManagerProvider;
import com.fasterxml.jackson.databind.Module;
import org.slf4j.spi.SLF4JServiceProvider;
open module io.xpipe.app {
@ -52,6 +51,7 @@ open module io.xpipe.app {
requires com.vladsch.flexmark;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.annotation;
requires net.synedra.validatorfx;
requires org.kordamp.ikonli.feather;
requires io.xpipe.modulefs;
@ -79,14 +79,6 @@ open module io.xpipe.app {
requires net.steppschuh.markdowngenerator;
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
requires jdk.charsets;
requires jdk.crypto.cryptoki;
@ -100,8 +92,8 @@ open module io.xpipe.app {
// For debugging
requires jdk.jdwp.agent;
requires org.kordamp.ikonli.core;
requires jdk.httpserver;
uses MessageExchangeImpl;
uses TerminalLauncher;
uses io.xpipe.app.ext.ActionProvider;
uses EventHandler;
@ -113,11 +105,11 @@ open module io.xpipe.app {
uses BrowserAction;
uses LicenseProvider;
uses io.xpipe.app.util.LicensedFeature;
uses io.xpipe.beacon.BeaconInterface;
provides Module with
AppJacksonModule;
provides ModuleLayerLoader with
MessageExchangeImpls.Loader,
DataStoreProviders.Loader,
ActionProvider.Loader,
PrefsProvider.Loader,
@ -132,26 +124,15 @@ open module io.xpipe.app {
AppLogs.Slf4jProvider;
provides EventHandler with
EventHandlerImpl;
provides MessageExchangeImpl with
ReadDrainExchangeImpl,
EditStoreExchangeImpl,
StoreProviderListExchangeImpl,
provides BeaconInterface with
OpenExchangeImpl,
LaunchExchangeImpl,
FocusExchangeImpl,
StatusExchangeImpl,
DrainExchangeImpl,
SinkExchangeImpl,
StopExchangeImpl,
HandshakeExchangeImpl,
ModeExchangeImpl,
DialogExchangeImpl,
RemoveStoreExchangeImpl,
RenameStoreExchangeImpl,
ListStoresExchangeImpl,
StoreAddExchangeImpl,
AskpassExchangeImpl,
TerminalWaitExchangeImpl,
TerminalLaunchExchangeImpl,
QueryStoreExchangeImpl,
VersionExchangeImpl;
}

View file

@ -1,67 +1,28 @@
package 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 com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.beacon.api.HandshakeExchange;
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.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;
import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
public class BeaconClient {
public class BeaconClient implements AutoCloseable {
private String token;
@Getter
private final AutoCloseable base;
private final InputStream in;
private final OutputStream out;
private BeaconClient(AutoCloseable base, InputStream in, OutputStream out) {
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);
public static BeaconClient establishConnection(BeaconClientInformation information) throws Exception {
var client = new BeaconClient();
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder().client(information).build());
client.token = response.getToken();
return client;
}
public static Optional<BeaconClient> tryEstablishConnection(ClientInformation information) {
public static Optional<BeaconClient> tryEstablishConnection(BeaconClientInformation information) {
try {
return Optional.of(establishConnection(information));
} catch (Exception ex) {
@ -69,246 +30,92 @@ public class BeaconClient implements AutoCloseable {
}
}
public void close() throws ConnectorException {
try {
base.close();
} catch (Exception ex) {
throw new ConnectorException("Couldn't close client", ex);
}
}
public InputStream receiveBody() throws ConnectorException {
try {
var sep = in.readNBytes(BODY_SEPARATOR.length);
if (sep.length != 0 && !Arrays.equals(BODY_SEPARATOR, sep)) {
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();
@SuppressWarnings("unchecked")
public <RES> RES performRequest(BeaconInterface<?> prov, String rawNode) throws
BeaconConnectorException, BeaconClientException, BeaconServerException {
var content = rawNode;
if (BeaconConfig.printMessages()) {
System.out.println("Sending raw request:");
System.out.println(content);
}
try (OutputStream blockOut = BeaconFormat.writeBlocks(out)) {
blockOut.write(content.getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
throw new ConnectorException("Couldn't write to socket", ex);
var client = HttpClient.newHttpClient();
HttpResponse<String> response;
try {
var uri = URI.create("http://localhost:" + BeaconConfig.getUsedPort() + 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());
}
private JsonNode receiveObject() throws ConnectorException, ClientException, ServerException {
JsonNode node;
try (InputStream blockIn = BeaconFormat.readBlocks(in)) {
node = JacksonMapper.getDefault().readTree(blockIn);
} catch (IOException ex) {
throw new ConnectorException("Couldn't read from socket", ex);
var httpRequest = builder
.uri(uri).POST(HttpRequest.BodyPublishers.ofString(content)).build();
response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
} catch (Exception ex) {
throw new BeaconConnectorException("Couldn't send request", ex);
}
if (BeaconConfig.printMessages()) {
System.out.println("Received response:");
System.out.println(node.toPrettyString());
System.out.println("Received raw response:");
System.out.println(response.body());
}
if (node.isMissingNode()) {
throw new ConnectorException("Received unexpected EOF");
}
var se = parseServerError(node);
var se = parseServerError(response);
if (se.isPresent()) {
se.get().throwError();
}
var ce = parseClientError(node);
var ce = parseClientError(response);
if (ce.isPresent()) {
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 {
var message = JacksonMapper.getDefault().treeToValue(content, ClientErrorMessage.class);
return Optional.of(message);
var reader = JacksonMapper.getDefault().readerFor(prov.getResponseClass());
var v = (RES) reader.readValue(response.body());
return v;
} 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 {
ObjectNode content = (ObjectNode) node.get("xPipeServerError");
if (content == null) {
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);
public <REQ, RES> RES performRequest(REQ req) throws BeaconConnectorException, BeaconClientException, BeaconServerException {
ObjectNode node = JacksonMapper.getDefault().valueToTree(req);
var prov = BeaconInterface.byRequest(req);
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 {
var reader = JacksonMapper.getDefault().readerFor(prov.get().getResponseClass());
return reader.readValue(content);
var v = JacksonMapper.getDefault().readValue(response.body(), BeaconClientErrorResponse.class);
return Optional.of(v);
} catch (IOException ex) {
throw new ConnectorException("Couldn't parse response", ex);
throw new BeaconConnectorException("Couldn't parse client error message", ex);
}
}
public InputStream getRawInputStream() {
return in;
private Optional<BeaconServerErrorResponse> parseServerError(HttpResponse<String> response) throws BeaconConnectorException {
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();
}
@JsonTypeName("cli")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class CliClientInformation extends ClientInformation {
@Override
public String toDisplayString() {
return "XPipe CLI";
try {
var v = JacksonMapper.getDefault().readValue(response.body(), BeaconServerErrorResponse.class);
return Optional.of(v);
} catch (IOException ex) {
throw new BeaconConnectorException("Couldn't parse client error message", ex);
}
}
@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;
import io.xpipe.beacon.ClientException;
package io.xpipe.beacon;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -12,11 +10,11 @@ import lombok.extern.jackson.Jacksonized;
@Builder
@Jacksonized
@AllArgsConstructor
public class ClientErrorMessage {
public class BeaconClientErrorResponse {
String message;
public ClientException throwException() {
return new ClientException(message);
public BeaconClientException throwException() {
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,77 @@
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 final CliClientInformation cli() {
return (CliClientInformation) this;
}
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("gateway")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class GatewayClientInformation extends BeaconClientInformation {
String version;
@Override
public String toDisplayString() {
return "XPipe Gateway " + version;
}
}
@JsonTypeName("api")
@Value
@Builder
@Jacksonized
@EqualsAndHashCode(callSuper = false)
public static class ApiClientInformation extends BeaconClientInformation {
String version;
String language;
@Override
public String toDisplayString() {
return String.format("XPipe %s API v%s", language, version);
}
}
}

View file

@ -1,15 +1,11 @@
package io.xpipe.beacon;
import io.xpipe.core.util.XPipeInstallation;
import lombok.experimental.UtilityClass;
import java.nio.charset.StandardCharsets;
@UtilityClass
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 DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs";
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 EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
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() {
if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) {

View file

@ -1,189 +1,27 @@
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;
@Getter
public abstract class BeaconConnection implements AutoCloseable {
@Getter
protected BeaconClient beaconClient;
private InputStream bodyInput;
private OutputStream bodyOutput;
protected abstract void constructSocket();
protected abstract void establishConnection() throws Exception;
@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");
throw new IllegalStateException("Socket is closed");
}
}
public OutputStream getOutputStream() {
public <REQ, RES> RES performSimpleExchange(REQ req) throws Exception {
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);
return beaconClient.performRequest(req);
}
}

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
public void setupModule(SetupContext context) {
context.registerSubtypes(
new NamedType(BeaconClient.ApiClientInformation.class),
new NamedType(BeaconClient.CliClientInformation.class),
new NamedType(BeaconClient.DaemonInformation.class));
new NamedType(BeaconClientInformation.ApiClientInformation.class),
new NamedType(BeaconClientInformation.CliClientInformation.class),
new NamedType(BeaconClientInformation.DaemonInformation.class));
}
}

View file

@ -1,6 +1,6 @@
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.store.FileNames;
import io.xpipe.core.util.XPipeDaemonMode;
@ -108,8 +108,7 @@ public class BeaconServer {
}
public static boolean tryStop(BeaconClient client) throws Exception {
client.sendRequest(StopExchange.Request.builder().build());
StopExchange.Response res = client.receiveResponse();
StopExchange.Response res = client.performRequest(StopExchange.Request.builder().build());
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.ResponseMessage;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.SecretValue;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
@ -11,17 +9,17 @@ import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
public class AskpassExchange implements MessageExchange {
public class AskpassExchange extends BeaconInterface<AskpassExchange.Request> {
@Override
public String getId() {
return "askpass";
public String getPath() {
return "/askpass";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
public static class Request {
UUID secretId;
@NonNull
@ -33,7 +31,7 @@ public class AskpassExchange implements MessageExchange {
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
public static class Response {
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;
import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage;
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
@ -11,17 +9,17 @@ import lombok.extern.jackson.Jacksonized;
import java.nio.file.Path;
import java.util.UUID;
public class TerminalLaunchExchange implements MessageExchange {
public class TerminalLaunchExchange extends BeaconInterface<TerminalLaunchExchange.Request> {
@Override
public String getId() {
return "terminalLaunch";
public String getPath() {
return "/terminalLaunch";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
public static class Request {
@NonNull
UUID request;
}
@ -29,7 +27,7 @@ public class TerminalLaunchExchange implements MessageExchange {
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
public static class Response {
@NonNull
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;
}
}

View file

@ -1,17 +0,0 @@
package io.xpipe.beacon.exchange.data;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.time.Instant;
@Value
@Jacksonized
@Builder
public class CollectionListEntry {
String name;
int size;
Instant lastUsed;
}

View file

@ -1,17 +0,0 @@
package io.xpipe.beacon.exchange.data;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.time.Instant;
@Value
@Jacksonized
@Builder
public class EntryListEntry {
String name;
String type;
String description;
Instant lastUsed;
}

View file

@ -1,14 +0,0 @@
package io.xpipe.beacon.exchange.data;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@Value
@Jacksonized
@Builder
public class ProviderEntry {
String id;
String description;
boolean hidden;
}

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