diff --git a/app/build.gradle b/app/build.gradle index d2a337a1..53c0e9dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,9 +56,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" @@ -96,9 +94,6 @@ run { systemProperty 'io.xpipe.app.logLevel', "trace" 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 for (final def e in System.getProperties().entrySet()) { diff --git a/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java new file mode 100644 index 00000000..4cc03a61 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java @@ -0,0 +1,104 @@ +package io.xpipe.app.beacon; + +import com.sun.net.httpserver.HttpServer; +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.issue.TrackEvent; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.beacon.BeaconConfig; +import io.xpipe.beacon.BeaconInterface; +import lombok.Getter; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executors; + +public class AppBeaconServer { + + private static AppBeaconServer INSTANCE; + @Getter + private final int port; + @Getter + private final boolean propertyPort; + private boolean running; + private HttpServer server; + @Getter + private final Set sessions = new HashSet<>(); + + static { + int port; + boolean propertyPort; + if (System.getProperty(BeaconConfig.BEACON_PORT_PROP) != null) { + port = BeaconConfig.getUsedPort(); + propertyPort = true; + } else { + port = AppPrefs.get().httpServerPort().getValue(); + propertyPort = false; + } + INSTANCE = new AppBeaconServer(port, propertyPort); + } + + private AppBeaconServer(int port, boolean propertyPort) { + this.port = port; + this.propertyPort = propertyPort; + } + + public static void init() { + try { + INSTANCE.start(); + TrackEvent.withInfo("Started http server") + .tag("port", INSTANCE.getPort()) + .build() + .handle(); + } catch (Exception ex) { + // Not terminal! + // We can still continue without the running server + ErrorEvent.fromThrowable(ex) + .description("Unable to start local http server on port " + INSTANCE.getPort()) + .build() + .handle(); + } + } + + public static void reset() { + if (INSTANCE != null) { + INSTANCE.stop(); + INSTANCE = null; + } + } + + public void addSession(BeaconSession session) { + this.sessions.add(session); + } + + public static AppBeaconServer get() { + return INSTANCE; + } + + private void stop() { + if (!running) { + return; + } + + running = false; + server.stop(1); + } + + private void start() throws IOException { + server = HttpServer.create(new InetSocketAddress("localhost", port), 10); + BeaconInterface.getAll().forEach(beaconInterface -> { + server.createContext(beaconInterface.getPath(), new BeaconRequestHandler<>(beaconInterface)); + }); + server.setExecutor(Executors.newSingleThreadExecutor(r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setName("http handler"); + t.setUncaughtExceptionHandler((t1, e) -> { + ErrorEvent.fromThrowable(e).handle(); + }); + return t; + })); + server.start(); + running = true; + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/BeaconRequestHandler.java b/app/src/main/java/io/xpipe/app/beacon/BeaconRequestHandler.java new file mode 100644 index 00000000..839a6672 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/BeaconRequestHandler.java @@ -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 implements HttpHandler { + + private final BeaconInterface beaconInterface; + + public BeaconRequestHandler(BeaconInterface 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 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)); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/BeaconSession.java b/app/src/main/java/io/xpipe/app/beacon/BeaconSession.java new file mode 100644 index 00000000..38cec0f2 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/BeaconSession.java @@ -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; +} diff --git a/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java similarity index 55% rename from app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java rename to app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java index 810d2c71..bfb70c3c 100644 --- a/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java @@ -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 { +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()); diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/FocusExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/FocusExchangeImpl.java new file mode 100644 index 00000000..0497915f --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/FocusExchangeImpl.java @@ -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(); +} +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/HandshakeExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/HandshakeExchangeImpl.java new file mode 100644 index 00000000..9c5077cb --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/HandshakeExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ModeExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ModeExchangeImpl.java new file mode 100644 index 00000000..0cb19663 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ModeExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/OpenExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/OpenExchangeImpl.java new file mode 100644 index 00000000..3109b176 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/OpenExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/StatusExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/StatusExchangeImpl.java new file mode 100644 index 00000000..883bf207 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/StatusExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/StopExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/StopExchangeImpl.java new file mode 100644 index 00000000..ee272d35 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/StopExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java new file mode 100644 index 00000000..097486bc --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java new file mode 100644 index 00000000..2114dacb --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/io/xpipe/app/exchange/VersionExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/VersionExchangeImpl.java similarity index 54% rename from app/src/main/java/io/xpipe/app/exchange/VersionExchangeImpl.java rename to app/src/main/java/io/xpipe/app/beacon/impl/VersionExchangeImpl.java index 9115b06f..d6fe83b9 100644 --- a/app/src/main/java/io/xpipe/app/exchange/VersionExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/VersionExchangeImpl.java @@ -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 { +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") + ")"; diff --git a/app/src/main/java/io/xpipe/app/core/AppProperties.java b/app/src/main/java/io/xpipe/app/core/AppProperties.java index bb173590..700c71dd 100644 --- a/app/src/main/java/io/xpipe/app/core/AppProperties.java +++ b/app/src/main/java/io/xpipe/app/core/AppProperties.java @@ -50,11 +50,10 @@ public class AppProperties { Properties props = new Properties(); props.load(Files.newInputStream(propsFile)); props.forEach((key, value) -> { - if (System.getProperty(key.toString()) != null) { - return; + // Don't overwrite existing properties + if (System.getProperty(key.toString()) == null) { + System.setProperty(key.toString(), value.toString()); } - - System.setProperty(key.toString(), value.toString()); }); } catch (IOException e) { ErrorEvent.fromThrowable(e).handle(); diff --git a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java deleted file mode 100644 index eb2ac169..00000000 --- a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java +++ /dev/null @@ -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> post = new AtomicReference<>(); - var res = prov.get() - .handleRequest( - new BeaconHandler() { - @Override - public void postResponse(FailableRunnable 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 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 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); - } -} diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index d3b8582d..63f840d9 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -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,12 +43,8 @@ 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(); - AppI18n.init(); AppCertutilCheck.check(); AppAvCheck.check(); AppSid.init(); @@ -56,8 +52,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 +81,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"); } } diff --git a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java index ad3633e6..44edc606 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java @@ -6,6 +6,7 @@ import io.xpipe.app.core.check.AppTempCheck; import io.xpipe.app.core.check.AppUserDirectoryCheck; import io.xpipe.app.issue.*; import io.xpipe.app.launcher.LauncherCommand; +import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.LocalShell; import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.ThreadHelper; @@ -109,6 +110,9 @@ public abstract class OperationMode { AppProperties.logArguments(args); AppProperties.logSystemProperties(); AppProperties.logPassedProperties(); + AppExtensionManager.init(true); + AppI18n.init(); + AppPrefs.initLocal(); TrackEvent.info("Finished initial setup"); } catch (Throwable ex) { ErrorEvent.fromThrowable(ex).term().handle(); diff --git a/app/src/main/java/io/xpipe/app/exchange/DialogExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/DialogExchangeImpl.java deleted file mode 100644 index b6d46c20..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/DialogExchangeImpl.java +++ /dev/null @@ -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 { - - private static final Map openDialogs = new HashMap<>(); - private static final Map> openDialogConsumers = new HashMap<>(); - - public static DialogReference add(Dialog d, FailableConsumer onCompletion) throws Exception { - return add(d, UUID.randomUUID(), onCompletion); - } - - public static DialogReference add(Dialog d, UUID uuid, FailableConsumer 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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/FocusExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/FocusExchangeImpl.java deleted file mode 100644 index 2b5181d5..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/FocusExchangeImpl.java +++ /dev/null @@ -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 { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) { - OperationMode.switchUp(OperationMode.map(msg.getMode())); - return Response.builder().build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java deleted file mode 100644 index a7dfb5a7..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java +++ /dev/null @@ -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 { - - @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 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; - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java deleted file mode 100644 index f73b7583..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java +++ /dev/null @@ -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 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; -} diff --git a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpls.java b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpls.java deleted file mode 100644 index 94a18b15..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpls.java +++ /dev/null @@ -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> ALL; - - @SuppressWarnings("unchecked") - public static Optional> byId( - String name) { - var r = ALL.stream().filter(d -> d.getId().equals(name)).findAny(); - return Optional.ofNullable((MessageExchangeImpl) r.orElse(null)); - } - - @SuppressWarnings("unchecked") - public static - Optional> byRequest(RQ req) { - var r = ALL.stream() - .filter(d -> d.getRequestClass().equals(req.getClass())) - .findAny(); - return Optional.ofNullable((MessageExchangeImpl) r.orElse(null)); - } - - public static List> 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()); - } - }); - } - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/OpenExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/OpenExchangeImpl.java deleted file mode 100644 index 9a560177..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/OpenExchangeImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.xpipe.app.exchange; - -import io.xpipe.app.core.mode.OperationMode; -import io.xpipe.app.launcher.LauncherInput; -import io.xpipe.app.util.PlatformState; -import io.xpipe.app.util.ThreadHelper; -import io.xpipe.beacon.BeaconHandler; -import io.xpipe.beacon.ServerException; -import io.xpipe.beacon.exchange.OpenExchange; - -public class OpenExchangeImpl extends OpenExchange - implements MessageExchangeImpl { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) throws ServerException { - if (msg.getArguments().isEmpty()) { - if (!OperationMode.switchToSyncIfPossible(OperationMode.GUI)) { - throw new ServerException(PlatformState.getLastError()); - } - } - - // Wait for startup - while (OperationMode.get() == null) { - ThreadHelper.sleep(100); - } - - LauncherInput.handle(msg.getArguments()); - return Response.builder().build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/QueryStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/QueryStoreExchangeImpl.java deleted file mode 100644 index 39f54659..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/QueryStoreExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/TerminalLaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/TerminalLaunchExchangeImpl.java deleted file mode 100644 index 5540de7f..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/TerminalLaunchExchangeImpl.java +++ /dev/null @@ -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 { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) throws ClientException { - var r = TerminalLauncherManager.performLaunch(msg.getRequest()); - return Response.builder().targetFile(r).build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/TerminalWaitExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/TerminalWaitExchangeImpl.java deleted file mode 100644 index bac98a9e..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/TerminalWaitExchangeImpl.java +++ /dev/null @@ -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 { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) throws ServerException, ClientException { - TerminalLauncherManager.waitForCompletion(msg.getRequest()); - return Response.builder().build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/DrainExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/DrainExchangeImpl.java deleted file mode 100644 index 5cdd9b15..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/DrainExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java deleted file mode 100644 index b13c3045..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/ListStoresExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/ListStoresExchangeImpl.java deleted file mode 100644 index 09a44977..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/ListStoresExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/ModeExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/ModeExchangeImpl.java deleted file mode 100644 index d08a88c6..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/ModeExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java deleted file mode 100644 index 2cc03a8f..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java +++ /dev/null @@ -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 { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) { - return ReadDrainExchange.Response.builder().build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java deleted file mode 100644 index 25ee3208..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java deleted file mode 100644 index eccd5546..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/SinkExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/SinkExchangeImpl.java deleted file mode 100644 index 79ddef7f..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/SinkExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StatusExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StatusExchangeImpl.java deleted file mode 100644 index 520a2e91..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StatusExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StopExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StopExchangeImpl.java deleted file mode 100644 index df02c0b6..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StopExchangeImpl.java +++ /dev/null @@ -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 { - - @Override - public Response handleRequest(BeaconHandler handler, Request msg) { - handler.postResponse(() -> { - ThreadHelper.runAsync(() -> { - ThreadHelper.sleep(1000); - OperationMode.close(); - }); - }); - return Response.builder().success(true).build(); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java deleted file mode 100644 index ee47a65a..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - 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()); - } -} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java deleted file mode 100644 index b8986104..00000000 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StoreProviderListExchangeImpl.java +++ /dev/null @@ -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 { - - @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(); - } -} diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java b/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java index b47f84e6..d2812962 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java @@ -1,5 +1,6 @@ package io.xpipe.app.launcher; +import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.core.AppDataLock; import io.xpipe.app.core.AppLogs; import io.xpipe.app.core.AppProperties; @@ -9,13 +10,13 @@ import io.xpipe.app.issue.LogErrorHandler; import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.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.BeaconClient; +import io.xpipe.beacon.BeaconClientInformation; +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; @@ -81,26 +82,25 @@ public class LauncherCommand implements Callable { private void checkStart() { try { - if (BeaconServer.isReachable()) { - try (var con = new LauncherConnection()) { - con.constructSocket(); - con.performSimpleExchange(FocusExchange.Request.builder() - .mode(getEffectiveMode()) - .build()); + var port = AppBeaconServer.get().getPort(); + var client = BeaconClient.tryEstablishConnection(port, BeaconClientInformation.DaemonInformation.builder().build()); + if (client.isPresent()) { + client.get().performRequest(FocusExchange.Request.builder().mode(getEffectiveMode()).build()); if (!inputs.isEmpty()) { - con.performSimpleExchange( + client.get().performRequest( OpenExchange.Request.builder().arguments(inputs).build()); } if (OsType.getLocal().equals(OsType.MACOS)) { Desktop.getDesktop().setOpenURIHandler(e -> { - con.performSimpleExchange(OpenExchange.Request.builder() - .arguments(List.of(e.getURI().toString())) - .build()); + try { + client.get().performRequest(OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build()); + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).expected().omit().handle(); + } }); ThreadHelper.sleep(1000); } - } TrackEvent.info("Another instance is already running on this port. Quitting ..."); OperationMode.halt(1); } diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java b/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java deleted file mode 100644 index fdbe0a25..00000000 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.xpipe.app.launcher; - -import io.xpipe.beacon.BeaconClient; -import io.xpipe.beacon.BeaconConnection; -import io.xpipe.beacon.BeaconException; - -public class LauncherConnection extends BeaconConnection { - - @Override - protected void constructSocket() { - try { - beaconClient = BeaconClient.establishConnection( - BeaconClient.DaemonInformation.builder().build()); - } catch (Exception ex) { - throw new BeaconException("Unable to connect to running xpipe daemon", ex); - } - } -} diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 2765b7b1..5e56d97c 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -14,14 +14,13 @@ import io.xpipe.app.terminal.ExternalTerminalType; import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.ModuleHelper; - +import io.xpipe.core.util.XPipeInstallation; import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableDoubleValue; import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableValue; - import lombok.Getter; import lombok.Value; @@ -121,6 +120,13 @@ public class AppPrefs { private final StringProperty lockCrypt = mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class); + final Property httpServerPort = + map(new SimpleObjectProperty<>(XPipeInstallation.getDefaultBeaconPort()), "httpServerPort", Integer.class); + + public ObservableValue httpServerPort() { + return httpServerPort; + } + private final IntegerProperty editorReloadTimeout = map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class); private final BooleanProperty confirmDeletions = @@ -153,6 +159,7 @@ public class AppPrefs { new SshCategory(), new LocalShellCategory(), new SecurityCategory(), + new HttpServerCategory(), new TroubleshootCategory(), new DeveloperCategory()) .filter(appPrefsCategory -> appPrefsCategory.show()) diff --git a/app/src/main/java/io/xpipe/app/prefs/HttpServerCategory.java b/app/src/main/java/io/xpipe/app/prefs/HttpServerCategory.java new file mode 100644 index 00000000..563c904a --- /dev/null +++ b/app/src/main/java/io/xpipe/app/prefs/HttpServerCategory.java @@ -0,0 +1,26 @@ +package io.xpipe.app.prefs; + +import io.xpipe.app.beacon.AppBeaconServer; +import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.util.OptionsBuilder; + +public class HttpServerCategory extends AppPrefsCategory { + + @Override + protected String getId() { + return "httpServer"; + } + + @Override + protected Comp create() { + var prefs = AppPrefs.get(); + return new OptionsBuilder() + .addTitle("httpServerConfiguration") + .sub(new OptionsBuilder() + .nameAndDescription("httpServerPort") + .addInteger(prefs.httpServerPort) + .disable(AppBeaconServer.get().isPropertyPort()) + ) + .buildComp(); + } +} diff --git a/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java b/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java index 936e34e4..152c02bf 100644 --- a/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java +++ b/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java @@ -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(); diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index a0d3e828..35756c47 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -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,15 +10,15 @@ 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 { + exports io.xpipe.app.beacon; exports io.xpipe.app.core; exports io.xpipe.app.util; exports io.xpipe.app; @@ -52,6 +52,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 +80,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 +93,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 +106,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 +125,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; } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index 8ccf6b9e..b5e2f7d8 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -1,314 +1,124 @@ 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 final int port; + private String token; - @Getter - private final AutoCloseable base; + public BeaconClient(int port) {this.port = port;} - 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(int port, BeaconClientInformation information) throws Exception { + var client = new BeaconClient(port); + HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder().client(information).build()); + client.token = response.getToken(); return client; } - public static Optional tryEstablishConnection(ClientInformation information) { + public static Optional tryEstablishConnection(int port, BeaconClientInformation information) { try { - return Optional.of(establishConnection(information)); + return Optional.of(establishConnection(port, information)); } catch (Exception ex) { return Optional.empty(); } } - public void close() throws ConnectorException { - try { - base.close(); - } catch (Exception ex) { - throw new ConnectorException("Couldn't close client", ex); - } - } - public InputStream receiveBody() throws ConnectorException { - 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 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 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); - } - } - - public 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 client = HttpClient.newHttpClient(); + HttpResponse response; + try { + var uri = URI.create("http://localhost:" + port + prov.getPath()); + var builder = HttpRequest.newBuilder(); + if (token != null) { + builder.header("Authorization", "Bearer " + token); + } + 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 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 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 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 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 parseClientError(HttpResponse 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; - } - - public OutputStream getRawOutputStream() { - return out; - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") - public abstract static class ClientInformation { - - public final CliClientInformation cli() { - return (CliClientInformation) this; + private Optional parseServerError(HttpResponse response) throws BeaconConnectorException { + if (response.statusCode() < 500 || response.statusCode() > 599) { + return Optional.empty(); } - 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); - } - } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ClientErrorMessage.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClientErrorResponse.java similarity index 53% rename from beacon/src/main/java/io/xpipe/beacon/exchange/data/ClientErrorMessage.java rename to beacon/src/main/java/io/xpipe/beacon/BeaconClientErrorResponse.java index 09fd318e..bd2c196a 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ClientErrorMessage.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClientErrorResponse.java @@ -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); } } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClientException.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClientException.java new file mode 100644 index 00000000..a561c777 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClientException.java @@ -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); + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClientInformation.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClientInformation.java new file mode 100644 index 00000000..635eb88c --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClientInformation.java @@ -0,0 +1,57 @@ +package io.xpipe.beacon; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type") +public abstract class BeaconClientInformation { + + public abstract String toDisplayString(); + + @JsonTypeName("cli") + @Value + @Builder + @Jacksonized + @EqualsAndHashCode(callSuper = false) + public static class CliClientInformation extends BeaconClientInformation { + + @Override + public String toDisplayString() { + return "XPipe CLI"; + } + } + + @JsonTypeName("daemon") + @Value + @Builder + @Jacksonized + @EqualsAndHashCode(callSuper = false) + public static class DaemonInformation extends BeaconClientInformation { + + @Override + public String toDisplayString() { + return "Daemon"; + } + } + + @JsonTypeName("api") + @Value + @Builder + @Jacksonized + @EqualsAndHashCode(callSuper = false) + public static class ApiClientInformation extends BeaconClientInformation { + + String name; + + @Override + public String toDisplayString() { + return name; + } + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java index 4e21f3d5..c62af056 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java @@ -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) { diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java deleted file mode 100644 index 509e6d71..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.xpipe.beacon; - -import io.xpipe.core.util.FailableBiConsumer; -import io.xpipe.core.util.FailableConsumer; - -import lombok.Getter; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public abstract class BeaconConnection implements AutoCloseable { - - @Getter - protected BeaconClient beaconClient; - - private InputStream bodyInput; - private OutputStream bodyOutput; - - protected abstract void constructSocket(); - - @Override - public void close() { - try { - if (beaconClient != null) { - beaconClient.close(); - } - beaconClient = null; - } catch (Exception e) { - beaconClient = null; - throw new BeaconException("Could not close beacon connection", e); - } - } - - public void withOutputStream(FailableConsumer ex) { - try { - ex.accept(getOutputStream()); - } catch (IOException e) { - throw new BeaconException("Could not write to beacon output stream", e); - } - } - - public void withInputStream(FailableConsumer ex) { - try { - ex.accept(getInputStream()); - } catch (IOException e) { - throw new BeaconException("Could not read from beacon output stream", e); - } - } - - public void checkClosed() { - if (beaconClient == null) { - throw new BeaconException("Socket is closed"); - } - } - - public OutputStream getOutputStream() { - checkClosed(); - - if (bodyOutput == null) { - throw new IllegalStateException("Body output has not started yet"); - } - - return bodyOutput; - } - - public InputStream getInputStream() { - checkClosed(); - - if (bodyInput == null) { - throw new IllegalStateException("Body input has not started yet"); - } - - return bodyInput; - } - - public void performInputExchange( - REQ req, FailableBiConsumer responseConsumer) { - checkClosed(); - - performInputOutputExchange(req, null, responseConsumer); - } - - public void performInputOutputExchange( - REQ req, - FailableConsumer reqWriter, - FailableBiConsumer 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 void sendRequest(REQ req) { - checkClosed(); - - try { - beaconClient.sendRequest(req); - } catch (Exception e) { - throw unwrapException(e); - } - } - - public 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 RES performOutputExchange( - REQ req, FailableConsumer reqWriter) { - checkClosed(); - - try { - beaconClient.sendRequest(req); - try (var out = sendBody()) { - reqWriter.accept(out); - } - return beaconClient.receiveResponse(); - } catch (Exception e) { - throw unwrapException(e); - } - } - - public 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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConnectorException.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConnectorException.java new file mode 100644 index 00000000..afdb86b2 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConnectorException.java @@ -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); + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconException.java b/beacon/src/main/java/io/xpipe/beacon/BeaconException.java deleted file mode 100644 index 5c4bd3b6..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconException.java +++ /dev/null @@ -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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java b/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java deleted file mode 100644 index 029f2305..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java +++ /dev/null @@ -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; - } - }; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java b/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java deleted file mode 100644 index 8b4a960b..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java +++ /dev/null @@ -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 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; -} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java b/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java new file mode 100644 index 00000000..28ed9f7b --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java @@ -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 { + + private static List> ALL; + + public static List> getAll() { + return ALL; + } + + public static Optional> byPath(String path) { + return ALL.stream() + .filter(d -> d.getPath().equals(path)) + .findAny(); + } + + + public static Optional> 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 getRequestClass() { + var c = getClass().getSuperclass(); + var name = (c.getSuperclass().equals(BeaconInterface.class) ? c : getClass()).getName() + "$Request"; + return (Class) Class.forName(name); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public Class getResponseClass() { + var c = getClass().getSuperclass(); + var name = (c.getSuperclass().equals(BeaconInterface.class) ? c : getClass()).getName() + "$Response"; + return (Class) 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(); + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java b/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java index 9a8da0e1..b89ca477 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java @@ -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)); } } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java index 50649fad..1e7c635a 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java @@ -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; @@ -18,9 +18,9 @@ import java.util.List; */ public class BeaconServer { - public static boolean isReachable() { + public static boolean isReachable(int port) { try (var socket = new Socket()) { - socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort()), 5000); + socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 5000); return true; } catch (Exception e) { return false; @@ -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(); } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServerErrorResponse.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServerErrorResponse.java new file mode 100644 index 00000000..28f63450 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServerErrorResponse.java @@ -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); + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServerException.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServerException.java new file mode 100644 index 00000000..8247bc0a --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServerException.java @@ -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); + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/ClientException.java b/beacon/src/main/java/io/xpipe/beacon/ClientException.java deleted file mode 100644 index 784d9b64..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/ClientException.java +++ /dev/null @@ -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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/ConnectorException.java b/beacon/src/main/java/io/xpipe/beacon/ConnectorException.java deleted file mode 100644 index 83a14dbc..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/ConnectorException.java +++ /dev/null @@ -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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/RequestMessage.java b/beacon/src/main/java/io/xpipe/beacon/RequestMessage.java deleted file mode 100644 index 9f9a5c2d..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/RequestMessage.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.xpipe.beacon; - -public interface RequestMessage {} diff --git a/beacon/src/main/java/io/xpipe/beacon/ResponseMessage.java b/beacon/src/main/java/io/xpipe/beacon/ResponseMessage.java deleted file mode 100644 index ec167994..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/ResponseMessage.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.xpipe.beacon; - -public interface ResponseMessage {} diff --git a/beacon/src/main/java/io/xpipe/beacon/ServerException.java b/beacon/src/main/java/io/xpipe/beacon/ServerException.java deleted file mode 100644 index d25c2272..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/ServerException.java +++ /dev/null @@ -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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/AskpassExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/AskpassExchange.java similarity index 53% rename from beacon/src/main/java/io/xpipe/beacon/exchange/AskpassExchange.java rename to beacon/src/main/java/io/xpipe/beacon/api/AskpassExchange.java index 8a1be7ca..e5f9f0ae 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/AskpassExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/api/AskpassExchange.java @@ -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 { @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; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/api/FocusExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/FocusExchange.java new file mode 100644 index 00000000..aac28bdb --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/FocusExchange.java @@ -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 { + + @Override + public String getPath() { + return "/focus"; + } + + @Jacksonized + @Builder + @Value + public static class Request { + @NonNull + XPipeDaemonMode mode; + } + + @Jacksonized + @Builder + @Value + public static class Response {} +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/HandshakeExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/HandshakeExchange.java new file mode 100644 index 00000000..2379db5d --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/HandshakeExchange.java @@ -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 { + + @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; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/ModeExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/ModeExchange.java new file mode 100644 index 00000000..c11a98ee --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/ModeExchange.java @@ -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 { + + @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; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/OpenExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/OpenExchange.java new file mode 100644 index 00000000..62172093 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/OpenExchange.java @@ -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 { + + @Override + public String getPath() { + return "/open"; + } + + @Jacksonized + @Builder + @Value + public static class Request { + @NonNull + List arguments; + } + + @Jacksonized + @Builder + @Value + public static class Response {} +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/StatusExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/StatusExchange.java new file mode 100644 index 00000000..0c4a2ead --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/StatusExchange.java @@ -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 { + + @Override + public String getPath() { + return "/status"; + } + + @Value + @Jacksonized + @Builder + public static class Request { + } + + @Jacksonized + @Builder + @Value + public static class Response { + String mode; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/StopExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/StopExchange.java new file mode 100644 index 00000000..abfcf693 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/StopExchange.java @@ -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 { + + @Override + public String getPath() { + return "/stop"; + } + + @Jacksonized + @Builder + @Value + public static class Request { + } + + @Jacksonized + @Builder + @Value + public static class Response { + boolean success; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/TerminalLaunchExchange.java similarity index 50% rename from beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java rename to beacon/src/main/java/io/xpipe/beacon/api/TerminalLaunchExchange.java index 6f314a64..82d53dbd 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/api/TerminalLaunchExchange.java @@ -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 { @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; } diff --git a/beacon/src/main/java/io/xpipe/beacon/api/TerminalWaitExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/TerminalWaitExchange.java new file mode 100644 index 00000000..da4d91d6 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/TerminalWaitExchange.java @@ -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 { + + @Override + public String getPath() { + return "/terminalWait"; + } + + @Jacksonized + @Builder + @Value + public static class Request { + @NonNull + UUID request; + } + + @Jacksonized + @Builder + @Value + public static class Response {} +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/VersionExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/VersionExchange.java new file mode 100644 index 00000000..42d4fa5e --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/api/VersionExchange.java @@ -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 { + + @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; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java deleted file mode 100644 index 573b2ad6..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/FocusExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/FocusExchange.java deleted file mode 100644 index f9082bda..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/FocusExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/LaunchExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/LaunchExchange.java deleted file mode 100644 index 2b1e03cc..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/LaunchExchange.java +++ /dev/null @@ -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 command; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchange.java deleted file mode 100644 index 999cd705..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchange.java +++ /dev/null @@ -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); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchanges.java b/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchanges.java deleted file mode 100644 index 54c0db98..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/MessageExchanges.java +++ /dev/null @@ -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 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 byId(String name) { - loadAll(); - return ALL.stream().filter(d -> d.getId().equals(name)).findAny(); - } - - public static Optional byRequest(RQ req) { - loadAll(); - return ALL.stream() - .filter(d -> d.getRequestClass().equals(req.getClass())) - .findAny(); - } - - public static Optional byResponse(RP rep) { - loadAll(); - return ALL.stream() - .filter(d -> d.getResponseClass().equals(rep.getClass())) - .findAny(); - } - - public static List getAll() { - loadAll(); - return ALL; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/OpenExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/OpenExchange.java deleted file mode 100644 index 29533f64..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/OpenExchange.java +++ /dev/null @@ -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 arguments; - } - - @Jacksonized - @Builder - @Value - public static class Response implements ResponseMessage {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/QueryStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/QueryStoreExchange.java deleted file mode 100644 index e656a72e..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/QueryStoreExchange.java +++ /dev/null @@ -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 config; - - DataStore internalStore; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java deleted file mode 100644 index 8f5f308e..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/StopExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/StopExchange.java deleted file mode 100644 index 6ee1475a..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/StopExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java deleted file mode 100644 index a0cf98f9..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/DialogExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/DialogExchange.java deleted file mode 100644 index dfa50dbc..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/DialogExchange.java +++ /dev/null @@ -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 getRequestClass() { - return DialogExchange.Request.class; - } - - @Override - public Class 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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/EditStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/EditStoreExchange.java deleted file mode 100644 index 351dd69e..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/EditStoreExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListCollectionsExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListCollectionsExchange.java deleted file mode 100644 index 270f0447..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListCollectionsExchange.java +++ /dev/null @@ -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 entries; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListEntriesExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListEntriesExchange.java deleted file mode 100644 index f7a0a577..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListEntriesExchange.java +++ /dev/null @@ -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 entries; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java deleted file mode 100644 index cb5377d6..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java +++ /dev/null @@ -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 entries; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ModeExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ModeExchange.java deleted file mode 100644 index b9ad4cfd..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ModeExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ReadDrainExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ReadDrainExchange.java deleted file mode 100644 index 1cc2bd2c..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ReadDrainExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java deleted file mode 100644 index bde33574..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java deleted file mode 100644 index 0c6dacf5..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java deleted file mode 100644 index 693179a7..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java deleted file mode 100644 index f7c9a98f..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java +++ /dev/null @@ -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 {} -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StatusExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StatusExchange.java deleted file mode 100644 index 7d08abae..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StatusExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java deleted file mode 100644 index 898a591b..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreProviderListExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreProviderListExchange.java deleted file mode 100644 index d7a173e9..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreProviderListExchange.java +++ /dev/null @@ -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> entries; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/VersionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/VersionExchange.java deleted file mode 100644 index 14155cd7..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/VersionExchange.java +++ /dev/null @@ -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; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/CollectionListEntry.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/CollectionListEntry.java deleted file mode 100644 index 93e80284..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/CollectionListEntry.java +++ /dev/null @@ -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; -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/EntryListEntry.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/EntryListEntry.java deleted file mode 100644 index 0df3fdb7..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/EntryListEntry.java +++ /dev/null @@ -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; -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ProviderEntry.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/ProviderEntry.java deleted file mode 100644 index 4b67c194..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ProviderEntry.java +++ /dev/null @@ -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; -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ServerErrorMessage.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/ServerErrorMessage.java deleted file mode 100644 index 5e0e6e20..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/ServerErrorMessage.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.xpipe.beacon.exchange.data; - -import io.xpipe.beacon.ServerException; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -import java.util.UUID; - -@SuppressWarnings("ClassCanBeRecord") -@Value -@Builder -@Jacksonized -@AllArgsConstructor -public class ServerErrorMessage { - - UUID requestId; - Throwable error; - - public void throwError() throws ServerException { - throw new ServerException(error.getMessage(), error); - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java deleted file mode 100644 index 1853245e..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.xpipe.beacon.exchange.data; - -import io.xpipe.core.store.DataStoreId; - -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -@Value -@Jacksonized -@Builder -public class StoreListEntry { - - DataStoreId id; - String type; - String information; -} diff --git a/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonController.java b/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonController.java index a5569604..72d6dc90 100644 --- a/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonController.java +++ b/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonController.java @@ -1,6 +1,8 @@ package io.xpipe.beacon.test; import io.xpipe.beacon.BeaconClient; +import io.xpipe.beacon.BeaconClientInformation; +import io.xpipe.beacon.BeaconConfig; import io.xpipe.beacon.BeaconServer; import io.xpipe.core.util.XPipeDaemonMode; import io.xpipe.core.util.XPipeInstallation; @@ -12,7 +14,7 @@ public class BeaconDaemonController { private static boolean alreadyStarted; public static void start(XPipeDaemonMode mode) throws Exception { - if (BeaconServer.isReachable()) { + if (BeaconServer.isReachable(BeaconConfig.getUsedPort())) { alreadyStarted = true; return; } @@ -27,7 +29,7 @@ public class BeaconDaemonController { } waitForStartup(process, custom); - if (!BeaconServer.isReachable()) { + if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) { throw new AssertionError(); } } @@ -37,13 +39,12 @@ public class BeaconDaemonController { return; } - if (!BeaconServer.isReachable()) { + if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) { return; } - var client = BeaconClient.establishConnection(BeaconClient.ApiClientInformation.builder() - .version("?") - .language("Java API Test") + var client = BeaconClient.establishConnection(BeaconConfig.getUsedPort(), BeaconClientInformation.ApiClientInformation.builder() + .name("Beacon daemon controller") .build()); if (!BeaconServer.tryStop(client)) { throw new AssertionError(); @@ -67,9 +68,8 @@ public class BeaconDaemonController { } catch (InterruptedException ignored) { } - var s = BeaconClient.tryEstablishConnection(BeaconClient.ApiClientInformation.builder() - .version("?") - .language("Java") + var s = BeaconClient.tryEstablishConnection(BeaconConfig.getUsedPort(), BeaconClientInformation.ApiClientInformation.builder() + .name("Beacon daemon controller") .build()); if (s.isPresent()) { return; @@ -86,7 +86,7 @@ public class BeaconDaemonController { } catch (InterruptedException ignored) { } - var r = BeaconServer.isReachable(); + var r = BeaconServer.isReachable(BeaconConfig.getUsedPort()); if (!r) { return; } diff --git a/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonExtensionTest.java b/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonExtensionTest.java index 9e1e1cdb..a66c469c 100644 --- a/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonExtensionTest.java +++ b/beacon/src/main/java/io/xpipe/beacon/test/BeaconDaemonExtensionTest.java @@ -1,9 +1,8 @@ package io.xpipe.beacon.test; import io.xpipe.core.process.OsType; -import io.xpipe.core.util.JacksonMapper; +import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.XPipeDaemonMode; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -11,7 +10,7 @@ public class BeaconDaemonExtensionTest { @BeforeAll public static void setup() throws Exception { - JacksonMapper.initModularized(ModuleLayer.boot()); + ModuleLayerLoader.loadAll(ModuleLayer.boot(),throwable -> throwable.printStackTrace()); BeaconDaemonController.start( OsType.getLocal().equals(OsType.WINDOWS) ? XPipeDaemonMode.TRAY : XPipeDaemonMode.BACKGROUND); } diff --git a/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java b/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java deleted file mode 100644 index d77b0720..00000000 --- a/beacon/src/main/java/io/xpipe/beacon/util/QuietDialogHandler.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.xpipe.beacon.util; - -import io.xpipe.beacon.BeaconConnection; -import io.xpipe.beacon.BeaconException; -import io.xpipe.beacon.exchange.cli.DialogExchange; -import io.xpipe.core.dialog.BaseQueryElement; -import io.xpipe.core.dialog.ChoiceElement; -import io.xpipe.core.dialog.DialogElement; -import io.xpipe.core.dialog.DialogReference; - -import java.util.Map; -import java.util.UUID; - -public class QuietDialogHandler { - - private final UUID dialogKey; - private final BeaconConnection connection; - private final Map overrides; - private DialogElement element; - - public QuietDialogHandler(DialogReference ref, BeaconConnection connection, Map overrides) { - this.dialogKey = ref.getDialogId(); - this.element = ref.getStart(); - this.connection = connection; - this.overrides = overrides; - } - - public static void handle(DialogReference ref, BeaconConnection connection) { - new QuietDialogHandler(ref, connection, Map.of()).handle(); - } - - public void handle() { - String response = null; - - if (element instanceof ChoiceElement c) { - response = handleChoice(c); - } - - if (element instanceof BaseQueryElement q) { - response = handleQuery(q); - } - - DialogExchange.Response res = connection.performSimpleExchange(DialogExchange.Request.builder() - .dialogKey(dialogKey) - .value(response) - .build()); - if (res.getElement() != null && element.equals(res.getElement())) { - throw new BeaconException( - "Invalid value for key " + res.getElement().toDisplayString()); - } - - element = res.getElement(); - - if (element != null) { - handle(); - } - } - - private String handleQuery(BaseQueryElement q) { - if (q.isRequired() && !overrides.containsKey(q.getDescription())) { - throw new IllegalStateException("Missing required config parameter: " + q.getDescription()); - } - - return overrides.get(q.getDescription()); - } - - private String handleChoice(ChoiceElement c) { - if (c.isRequired() && !overrides.containsKey(c.getDescription())) { - throw new IllegalStateException("Missing required config parameter: " + c.getDescription()); - } - - return overrides.get(c.getDescription()); - } -} diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index 64ed1985..cc55ad4e 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -1,49 +1,32 @@ -import io.xpipe.beacon.BeaconJacksonModule; -import io.xpipe.beacon.exchange.*; -import io.xpipe.beacon.exchange.cli.*; -import io.xpipe.core.util.ProxyFunction; - import com.fasterxml.jackson.databind.Module; +import io.xpipe.beacon.BeaconInterface; +import io.xpipe.beacon.BeaconJacksonModule; +import io.xpipe.beacon.api.*; +import io.xpipe.core.util.ModuleLayerLoader; open module io.xpipe.beacon { exports io.xpipe.beacon; - exports io.xpipe.beacon.exchange; - exports io.xpipe.beacon.exchange.data; - exports io.xpipe.beacon.exchange.cli; - exports io.xpipe.beacon.util; exports io.xpipe.beacon.test; + exports io.xpipe.beacon.api; - requires static com.fasterxml.jackson.core; - requires static com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.annotation; + requires com.fasterxml.jackson.databind; requires transitive io.xpipe.core; requires static lombok; requires static org.junit.jupiter.api; + requires jdk.httpserver; + requires java.net.http; - uses MessageExchange; - uses ProxyFunction; + uses io.xpipe.beacon.BeaconInterface; + provides ModuleLayerLoader with + BeaconInterface.Loader; provides Module with BeaconJacksonModule; - provides io.xpipe.beacon.exchange.MessageExchange with - SinkExchange, - DrainExchange, - LaunchExchange, - EditStoreExchange, - StoreProviderListExchange, - ModeExchange, - QueryStoreExchange, - StatusExchange, - FocusExchange, - OpenExchange, - StopExchange, - RenameStoreExchange, - RemoveStoreExchange, - StoreAddExchange, - ReadDrainExchange, + provides BeaconInterface with ModeExchange,StatusExchange, FocusExchange, OpenExchange, StopExchange, HandshakeExchange, AskpassExchange, TerminalWaitExchange, TerminalLaunchExchange, - ListStoresExchange, - DialogExchange, VersionExchange; } diff --git a/beacon/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader b/beacon/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader new file mode 100644 index 00000000..c70568c8 --- /dev/null +++ b/beacon/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader @@ -0,0 +1 @@ +io.xpipe.beacon.BeaconInterface$Loader \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index db7993e7..df895257 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -14,9 +14,7 @@ compileJava { dependencies { api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.1" - implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.17.1" implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.17.1" - implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.17.1" } version = rootProject.versionString diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialects.java b/core/src/main/java/io/xpipe/core/process/ShellDialects.java index c17fb2b8..3110814b 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialects.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialects.java @@ -58,10 +58,15 @@ public class ShellDialects { @Override public void init(ModuleLayer layer) { - ServiceLoader.load(layer, ShellDialect.class).stream().forEach(moduleLayerLoaderProvider -> { + var services = layer != null ? ServiceLoader.load(layer, ShellDialect.class) : ServiceLoader.load(ShellDialect.class); + services.stream().forEach(moduleLayerLoaderProvider -> { ALL.add(moduleLayerLoaderProvider.get()); }); + if (ALL.isEmpty()) { + return; + } + CMD = byId("cmd"); POWERSHELL = byId("powershell"); POWERSHELL_CORE = byId("pwsh"); diff --git a/core/src/main/java/io/xpipe/core/util/JacksonMapper.java b/core/src/main/java/io/xpipe/core/util/JacksonMapper.java index 8699ffbd..0b3dd93c 100644 --- a/core/src/main/java/io/xpipe/core/util/JacksonMapper.java +++ b/core/src/main/java/io/xpipe/core/util/JacksonMapper.java @@ -49,14 +49,14 @@ public class JacksonMapper { mapper.accept(INSTANCE); } - public static synchronized void initClassBased() { - initModularized(null); - } + public static class Loader implements ModuleLayerLoader { - public static synchronized void initModularized(ModuleLayer layer) { - List MODULES = findModules(layer); - INSTANCE.registerModules(MODULES); - init = true; + @Override + public void init(ModuleLayer layer) { + List MODULES = findModules(layer); + INSTANCE.registerModules(MODULES); + init = true; + } } private static List findModules(ModuleLayer layer) { diff --git a/core/src/main/java/io/xpipe/core/util/ModuleLayerLoader.java b/core/src/main/java/io/xpipe/core/util/ModuleLayerLoader.java index 216c97ec..e78dcab2 100644 --- a/core/src/main/java/io/xpipe/core/util/ModuleLayerLoader.java +++ b/core/src/main/java/io/xpipe/core/util/ModuleLayerLoader.java @@ -6,7 +6,8 @@ import java.util.function.Consumer; public interface ModuleLayerLoader { static void loadAll(ModuleLayer layer, Consumer errorHandler) { - ServiceLoader.load(layer, ModuleLayerLoader.class).stream().forEach(moduleLayerLoaderProvider -> { + var loaded = layer != null ? ServiceLoader.load(layer, ModuleLayerLoader.class) : ServiceLoader.load(ModuleLayerLoader.class); + loaded.stream().forEach(moduleLayerLoaderProvider -> { var instance = moduleLayerLoaderProvider.get(); try { instance.init(layer); diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index 4f89bb1a..e827bd26 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -23,11 +23,7 @@ public class XPipeInstallation { public static int getDefaultBeaconPort() { var offset = isStaging() ? 1 : 0; - if (OsType.getLocal().equals(OsType.WINDOWS)) { - return 21721 + offset; - } else { - return 21721 + 2 + offset; - } + return 21721 + offset; } private static String getPkgId() { diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 287db7ad..0bb231b2 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -2,6 +2,7 @@ import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ShellDialect; import io.xpipe.core.process.ShellDialects; import io.xpipe.core.util.CoreJacksonModule; +import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.ModuleLayerLoader; open module io.xpipe.core { @@ -11,9 +12,9 @@ open module io.xpipe.core { exports io.xpipe.core.process; requires com.fasterxml.jackson.datatype.jsr310; - requires com.fasterxml.jackson.module.paramnames; - requires static com.fasterxml.jackson.core; - requires static com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.annotation; + requires com.fasterxml.jackson.databind; requires java.net.http; requires static lombok; @@ -24,7 +25,7 @@ open module io.xpipe.core { uses ModuleLayerLoader; uses ShellDialect; - provides ModuleLayerLoader with + provides ModuleLayerLoader with JacksonMapper.Loader, ShellDialects.Loader; provides com.fasterxml.jackson.databind.Module with CoreJacksonModule; diff --git a/core/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader b/core/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader new file mode 100644 index 00000000..c61f23fd --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.xpipe.core.util.ModuleLayerLoader @@ -0,0 +1,2 @@ +io.xpipe.core.util.JacksonMapper$Loader +io.xpipe.core.process.ShellDialects$Loader \ No newline at end of file diff --git a/dist/changelogs/10.0.md b/dist/changelogs/10.0.md new file mode 100644 index 00000000..74e0683a --- /dev/null +++ b/dist/changelogs/10.0.md @@ -0,0 +1,3 @@ +This is an experimental implementation of an HTTP server to provide an API for XPipe that is easy to use and access. The existing interface used by the CLI has been completely ported over to that, so there might be a few behavior changes for the CLI. + +## It is not recommended to use this version for now unless you're interested in development. diff --git a/dist/licenses/prettytime.license b/dist/licenses/prettytime.license deleted file mode 100644 index f433b1a5..00000000 --- a/dist/licenses/prettytime.license +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/dist/licenses/prettytime.properties b/dist/licenses/prettytime.properties deleted file mode 100644 index 0c67b412..00000000 --- a/dist/licenses/prettytime.properties +++ /dev/null @@ -1,4 +0,0 @@ -name=Prettytime -version=5.0.7.Final -license=Apache License 2.0 -link=https://github.com/ocpsoft/prettytime \ No newline at end of file diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index 03a689f1..a3d6095f 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -27,6 +27,7 @@ open module io.xpipe.ext.base { requires java.desktop; requires io.xpipe.core; requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.annotation; requires java.net.http; requires static lombok; requires static javafx.controls; diff --git a/gradle/gradle_scripts/dev_default.properties b/gradle/gradle_scripts/dev_default.properties index f12fd39e..cc12f335 100644 --- a/gradle/gradle_scripts/dev_default.properties +++ b/gradle/gradle_scripts/dev_default.properties @@ -12,3 +12,6 @@ io.xpipe.app.showcase=false # Location in which your local development connection should be stored. If left empty, it will use your global XPipe storage in ~/.xpipe. io.xpipe.app.dataDir=local + +# When enabled, all http server input and output is printed. Useful for debugging +io.xpipe.beacon.printMessages=false diff --git a/gradle/gradle_scripts/java.gradle b/gradle/gradle_scripts/java.gradle index ee14af1d..a42f806f 100644 --- a/gradle/gradle_scripts/java.gradle +++ b/gradle/gradle_scripts/java.gradle @@ -22,7 +22,7 @@ javadoc{ addStringOption('link', 'https://docs.oracle.com/en/java/javase/21/docs/api/') addBooleanOption('html5', true) addStringOption('Xdoclint:none', '-quiet') - addBooleanOption('-enable-preview', true) + // addBooleanOption('-enable-preview', true) } } diff --git a/gradle/gradle_scripts/local_junit_suite.gradle b/gradle/gradle_scripts/local_junit_suite.gradle index a8340811..989adaee 100644 --- a/gradle/gradle_scripts/local_junit_suite.gradle +++ b/gradle/gradle_scripts/local_junit_suite.gradle @@ -24,7 +24,7 @@ testing { systemProperty 'io.xpipe.app.fullVersion', "true" systemProperty 'io.xpipe.beacon.printDaemonOutput', "false" systemProperty 'io.xpipe.app.useVirtualThreads', "false" - // systemProperty "io.xpipe.beacon.port", "21725" + systemProperty "io.xpipe.beacon.port", "21723" systemProperty "io.xpipe.beacon.launchDebugDaemon", "true" systemProperty "io.xpipe.app.dataDir", "$projectDir/local/" systemProperty "io.xpipe.app.logLevel", "trace" diff --git a/gradle/gradle_scripts/modules.gradle b/gradle/gradle_scripts/modules.gradle index 1b6bb40e..56059780 100644 --- a/gradle/gradle_scripts/modules.gradle +++ b/gradle/gradle_scripts/modules.gradle @@ -24,12 +24,6 @@ extraJavaModuleInfo { } } -extraJavaModuleInfo { - module("org.ocpsoft.prettytime:prettytime", "org.ocpsoft.prettytime") { - exportAllPackages() - } -} - extraJavaModuleInfo { module("com.vladsch.flexmark:flexmark", "com.vladsch.flexmark") { mergeJar('com.vladsch.flexmark:flexmark-util') diff --git a/gradle/gradle_scripts/remote_junit_suite.gradle b/gradle/gradle_scripts/remote_junit_suite.gradle index 6df6e012..d6a9844f 100644 --- a/gradle/gradle_scripts/remote_junit_suite.gradle +++ b/gradle/gradle_scripts/remote_junit_suite.gradle @@ -27,7 +27,7 @@ testing { systemProperty "io.xpipe.beacon.customDaemonCommand", "\"$rootDir/gradlew\" --console=plain $daemonCommand" } systemProperty "io.xpipe.beacon.daemonArgs", - " -Dio.xpipe.beacon.port=21725" + + " -Dio.xpipe.beacon.port=21723" + " -Dio.xpipe.app.dataDir=$projectDir/local/" + " -Dio.xpipe.storage.persist=false" + " -Dio.xpipe.app.writeSysOut=true" + @@ -36,7 +36,7 @@ testing { " -Dio.xpipe.app.logLevel=trace" systemProperty 'io.xpipe.beacon.printDaemonOutput', "true" - systemProperty "io.xpipe.beacon.port", "21725" + systemProperty "io.xpipe.beacon.port", "21723" systemProperty "io.xpipe.beacon.launchDebugDaemon", "true" } } diff --git a/lang/app/strings/translations_en.properties b/lang/app/strings/translations_en.properties index 026f8491..4b195419 100644 --- a/lang/app/strings/translations_en.properties +++ b/lang/app/strings/translations_en.properties @@ -459,3 +459,9 @@ addNotes=Add notes order=Order ... stickToTop=Keep on top orderAheadOf=Order ahead of ... +httpServer=HTTP server +httpServerConfiguration=HTTP server configuration +#context: networking +httpServerPort=Port +#context: networking +httpServerPortDescription=The port on which the HTTP server will listen on.\n\nNote that if you change this, any other applications that interact with the server need to be configured to use the new port as well.\n\nRequires a restart to apply. diff --git a/version b/version index 0359f243..2f52450b 100644 --- a/version +++ b/version @@ -1 +1 @@ -9.4 +10.0