diff --git a/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java b/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java index 0645d941..6e11cfd1 100644 --- a/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java +++ b/api/src/main/java/io/xpipe/api/connector/XPipeApiConnection.java @@ -78,7 +78,7 @@ public final class XPipeApiConnection extends BeaconConnection { } catch (InterruptedException ignored) { } - var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder() + var s = BeaconClient.tryEstablishConnection(BeaconClient.ApiClientInformation.builder() .version("?") .language("Java") .build()); @@ -96,7 +96,7 @@ public final class XPipeApiConnection extends BeaconConnection { } catch (InterruptedException ignored) { } - var r = BeaconServer.isRunning(); + var r = BeaconServer.isReachable(); if (!r) { return; } @@ -105,7 +105,7 @@ public final class XPipeApiConnection extends BeaconConnection { @Override protected void constructSocket() { - if (!BeaconServer.isRunning()) { + if (!BeaconServer.isReachable()) { try { start(); } catch (Exception ex) { @@ -122,7 +122,7 @@ public final class XPipeApiConnection extends BeaconConnection { } try { - beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder() + beaconClient = BeaconClient.establishConnection(BeaconClient.ApiClientInformation.builder() .version("?") .language("Java") .build()); diff --git a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java index 885ea09f..6088d530 100644 --- a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java +++ b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java @@ -192,6 +192,9 @@ public class AppSocketServer { } 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() .category("beacon") diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java index bcb7204f..8f1fc481 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java @@ -139,7 +139,11 @@ public class ErrorHandlerComp extends SimpleComp { var r = JfxHelper.createNamedEntry(a.getName(), a.getDescription()); var b = new ButtonComp(null, r, () -> { takenAction.setValue(a); - if (a.handle(event)) { + try { + if (a.handle(event)) { + stage.close(); + } + } catch (Exception ignored) { stage.close(); } }); 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 e020c97e..83ee7b46 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java @@ -2,6 +2,7 @@ package io.xpipe.app.launcher; import io.xpipe.app.core.AppDataLock; import io.xpipe.app.core.AppLogs; +import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.mode.OperationMode; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.LogErrorHandler; @@ -18,6 +19,7 @@ import lombok.SneakyThrows; import picocli.CommandLine; import java.awt.*; +import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; @@ -69,7 +71,7 @@ public class LauncherCommand implements Callable { private void checkStart() { try { - if (BeaconServer.isRunning()) { + if (BeaconServer.isReachable()) { try (var con = new LauncherConnection()) { con.constructSocket(); con.performSimpleExchange(FocusExchange.Request.builder().mode(getEffectiveMode()).build()); @@ -87,19 +89,18 @@ public class LauncherCommand implements Callable { TrackEvent.info("Another instance is already running on this port. Quitting ..."); OperationMode.halt(1); } + + // Even in case we are unable to reach another beacon server + // there might be another instance running, for example + // starting up or listening on another port + if (!AppDataLock.lock()) { + throw new IOException("Data directory " + AppProperties.get().getDataDir().toString() + " is already locked"); + } } catch (Exception ex) { var cli = XPipeInstallation.getLocalDefaultCliExecutable(); - ErrorEvent.fromThrowable(ex).description("Unable to connect to existing running daemon instance as it did not respond." + + ErrorEvent.fromThrowable(ex).term().description("Unable to connect to existing running daemon instance as it did not respond." + " Either try to kill the process xpiped manually or use the command " + cli + " daemon stop --force.").handle(); } - - // Even in case we are unable to reach another beacon server - // there might be another instance running, for example - // starting up or listening on another port - if (!AppDataLock.lock()) { - TrackEvent.info("Data directory is already locked. Quitting ..."); - OperationMode.halt(1); - } } private XPipeDaemonMode getEffectiveMode() { diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java b/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java index 1df024c1..fdbe0a25 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherConnection.java @@ -9,7 +9,7 @@ public class LauncherConnection extends BeaconConnection { @Override protected void constructSocket() { try { - beaconClient = BeaconClient.connect( + beaconClient = BeaconClient.establishConnection( BeaconClient.DaemonInformation.builder().build()); } catch (Exception ex) { throw new BeaconException("Unable to connect to running xpipe daemon", ex); diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index 2e8a1628..4329147c 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -48,12 +48,17 @@ public class BeaconClient implements AutoCloseable { this.out = out; } - public static BeaconClient connect(ClientInformation information) throws Exception { + public static BeaconClient establishConnection(ClientInformation information) throws Exception { var socket = new Socket(); socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort()), 5000); socket.setSoTimeout(5000); var client = new BeaconClient(socket, socket.getInputStream(), socket.getOutputStream()); client.sendObject(JacksonMapper.getDefault().valueToTree(information)); + var res = client.receiveObject(); + if (!res.isTextual() || !"ACK".equals(res.asText())) { + throw new BeaconException("Daemon responded with invalid acknowledgement"); + } + socket.setSoTimeout(0); return client; } @@ -102,9 +107,9 @@ public class BeaconClient implements AutoCloseable { }; } - public static Optional tryConnect(ClientInformation information) { + public static Optional tryEstablishConnection(ClientInformation information) { try { - return Optional.of(connect(information)); + return Optional.of(establishConnection(information)); } catch (Exception ex) { return Optional.empty(); } @@ -190,6 +195,10 @@ public class BeaconClient implements AutoCloseable { } 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); @@ -216,7 +225,7 @@ public class BeaconClient implements AutoCloseable { throw ce.get().throwException(); } - return parseResponse(node); + return node; } private Optional parseClientError(JsonNode node) throws ConnectorException { @@ -305,18 +314,6 @@ public class BeaconClient implements AutoCloseable { } } - @JsonTypeName("reachableCheck") - @Value - @Builder - @Jacksonized - @EqualsAndHashCode(callSuper = false) - public static class ReachableCheckInformation extends ClientInformation { - - @Override - public String toDisplayString() { - return "Reachable check"; - } - } @JsonTypeName("daemon") @Value diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java b/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java index 160a569a..c0927208 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconDaemonController.java @@ -10,7 +10,7 @@ public class BeaconDaemonController { private static boolean alreadyStarted; public static void start(XPipeDaemonMode mode) throws Exception { - if (BeaconServer.isRunning()) { + if (BeaconServer.isReachable()) { alreadyStarted = true; return; } @@ -25,7 +25,7 @@ public class BeaconDaemonController { } waitForStartup(process, custom); - if (!BeaconServer.isRunning()) { + if (!BeaconServer.isReachable()) { throw new AssertionError(); } } @@ -35,11 +35,11 @@ public class BeaconDaemonController { return; } - if (!BeaconServer.isRunning()) { + if (!BeaconServer.isReachable()) { return; } - var client = BeaconClient.connect(BeaconClient.ApiClientInformation.builder() + var client = BeaconClient.establishConnection(BeaconClient.ApiClientInformation.builder() .version("?") .language("Java API Test") .build()); @@ -65,7 +65,7 @@ public class BeaconDaemonController { } catch (InterruptedException ignored) { } - var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder() + var s = BeaconClient.tryEstablishConnection(BeaconClient.ApiClientInformation.builder() .version("?") .language("Java") .build()); @@ -84,7 +84,7 @@ public class BeaconDaemonController { } catch (InterruptedException ignored) { } - var r = BeaconServer.isRunning(); + var r = BeaconServer.isReachable(); if (!r) { return; } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java b/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java index 08b05fa4..9a8da0e1 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconJacksonModule.java @@ -10,7 +10,6 @@ public class BeaconJacksonModule extends SimpleModule { context.registerSubtypes( new NamedType(BeaconClient.ApiClientInformation.class), new NamedType(BeaconClient.CliClientInformation.class), - new NamedType(BeaconClient.DaemonInformation.class), - new NamedType(BeaconClient.ReachableCheckInformation.class)); + new NamedType(BeaconClient.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 08bac623..0d245155 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java @@ -8,6 +8,9 @@ import io.xpipe.core.util.XPipeInstallation; import java.io.BufferedReader; import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; import java.util.List; /** @@ -15,9 +18,9 @@ import java.util.List; */ public class BeaconServer { - public static boolean isRunning() { - try (var ignored = BeaconClient.connect( - BeaconClient.ReachableCheckInformation.builder().build())) { + public static boolean isReachable() { + try (var socket = new Socket()) { + socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort()), 5000); return true; } catch (Exception e) { return false;