mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-10-04 06:07:46 +00:00
Squash merge branch 18-release into master
This commit is contained in:
@@ -2,7 +2,6 @@ package io.xpipe.beacon;
|
||||
|
||||
import io.xpipe.beacon.api.HandshakeExchange;
|
||||
import io.xpipe.core.JacksonMapper;
|
||||
import io.xpipe.core.XPipeInstallation;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -26,7 +25,7 @@ public class BeaconClient {
|
||||
|
||||
public static BeaconClient establishConnection(int port, BeaconClientInformation information) throws Exception {
|
||||
var client = new BeaconClient(port);
|
||||
var auth = Files.readString(XPipeInstallation.getLocalBeaconAuthFile());
|
||||
var auth = Files.readString(BeaconConfig.getLocalBeaconAuthFile());
|
||||
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder()
|
||||
.client(information)
|
||||
.auth(BeaconAuthMethod.Local.builder().authFileContent(auth).build())
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package io.xpipe.beacon;
|
||||
|
||||
import io.xpipe.core.XPipeInstallation;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
@UtilityClass
|
||||
public class BeaconConfig {
|
||||
|
||||
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";
|
||||
private static final String LAUNCH_DAEMON_IN_DEBUG_PROP = "io.xpipe.beacon.launchDebugDaemon";
|
||||
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";
|
||||
|
||||
public static boolean printMessages() {
|
||||
if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) {
|
||||
@@ -22,27 +19,6 @@ public class BeaconConfig {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean launchDaemonInDebugMode() {
|
||||
if (System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean attachDebuggerToDaemon() {
|
||||
if (System.getProperty(ATTACH_DEBUGGER_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(ATTACH_DEBUGGER_PROP));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean printDaemonOutput() {
|
||||
if (System.getProperty(EXEC_DEBUG_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(EXEC_DEBUG_PROP));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getUsedPort() {
|
||||
var beaconPort = System.getenv("BEACON_PORT");
|
||||
if (beaconPort != null && !beaconPort.isBlank()) {
|
||||
@@ -53,22 +29,21 @@ public class BeaconConfig {
|
||||
return Integer.parseInt(System.getProperty(BEACON_PORT_PROP));
|
||||
}
|
||||
|
||||
return XPipeInstallation.getDefaultBeaconPort();
|
||||
return getDefaultBeaconPort();
|
||||
}
|
||||
|
||||
public static String getCustomDaemonCommand() {
|
||||
if (System.getProperty(EXEC_PROCESS_PROP) != null) {
|
||||
return System.getProperty(EXEC_PROCESS_PROP);
|
||||
}
|
||||
|
||||
return null;
|
||||
public static int getDefaultBeaconPort() {
|
||||
var staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
var offset = staging ? 1 : 0;
|
||||
return 21721 + offset;
|
||||
}
|
||||
|
||||
public static String getDaemonArguments() {
|
||||
if (System.getProperty(DAEMON_ARGUMENTS_PROP) != null) {
|
||||
return System.getProperty(DAEMON_ARGUMENTS_PROP);
|
||||
}
|
||||
|
||||
return null;
|
||||
public static Path getLocalBeaconAuthFile() {
|
||||
var staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
return Path.of(System.getProperty("java.io.tmpdir"), staging ? "xpipe_ptb_auth" : "xpipe_auth");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,24 +28,6 @@ public abstract class BeaconInterface<T> {
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public static class Loader implements ModuleLayerLoader {
|
||||
|
||||
@Override
|
||||
public void init(ModuleLayer layer) {
|
||||
var services = layer != null
|
||||
? ServiceLoader.load(layer, BeaconInterface.class)
|
||||
: ServiceLoader.load(BeaconInterface.class);
|
||||
ALL = services.stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.map(beaconInterface -> (BeaconInterface<?>) beaconInterface)
|
||||
.collect(Collectors.toList());
|
||||
// Remove parent classes
|
||||
ALL.removeIf(beaconInterface -> ALL.stream()
|
||||
.anyMatch(other -> !other.equals(beaconInterface)
|
||||
&& beaconInterface.getClass().isAssignableFrom(other.getClass())));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public Class<T> getRequestClass() {
|
||||
@@ -91,4 +73,22 @@ public abstract class BeaconInterface<T> {
|
||||
public Object getSynchronizationObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
package io.xpipe.beacon;
|
||||
|
||||
import io.xpipe.beacon.api.DaemonStopExchange;
|
||||
import io.xpipe.core.FilePath;
|
||||
import io.xpipe.core.OsType;
|
||||
import io.xpipe.core.XPipeDaemonMode;
|
||||
import io.xpipe.core.XPipeInstallation;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains basic functionality to start, communicate, and stop a remote beacon server.
|
||||
*/
|
||||
public class BeaconServer {
|
||||
|
||||
@SneakyThrows
|
||||
@@ -42,109 +32,9 @@ public class BeaconServer {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> toProcessCommand(String toExec) {
|
||||
// Having the trailing space is very important to force cmd to not interpret surrounding spaces and removing
|
||||
// them
|
||||
return OsType.getLocal().equals(OsType.WINDOWS)
|
||||
? List.of("cmd", "/c", toExec + " ")
|
||||
: List.of("sh", "-c", toExec);
|
||||
}
|
||||
|
||||
public static Process tryStartCustom() throws Exception {
|
||||
var custom = BeaconConfig.getCustomDaemonCommand();
|
||||
if (custom != null) {
|
||||
var toExec =
|
||||
custom + (BeaconConfig.getDaemonArguments() != null ? " " + BeaconConfig.getDaemonArguments() : "");
|
||||
var command = toProcessCommand(toExec);
|
||||
Process process = Runtime.getRuntime().exec(command.toArray(String[]::new));
|
||||
printDaemonOutput(process, command);
|
||||
return process;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Process start(String installationBase, XPipeDaemonMode mode) throws Exception {
|
||||
String command;
|
||||
if (!BeaconConfig.launchDaemonInDebugMode()) {
|
||||
command = XPipeInstallation.createExternalAsyncLaunchCommand(
|
||||
installationBase, mode, BeaconConfig.getDaemonArguments(), false);
|
||||
} else {
|
||||
command = XPipeInstallation.createExternalLaunchCommand(
|
||||
getDaemonDebugExecutable(installationBase), BeaconConfig.getDaemonArguments(), mode);
|
||||
}
|
||||
|
||||
var fullCommand = toProcessCommand(command);
|
||||
Process process = new ProcessBuilder(fullCommand).start();
|
||||
printDaemonOutput(process, fullCommand);
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void printDaemonOutput(Process proc, List<String> command) {
|
||||
boolean print = BeaconConfig.printDaemonOutput();
|
||||
if (print) {
|
||||
System.out.println("Starting daemon: " + command);
|
||||
}
|
||||
|
||||
var out = new Thread(
|
||||
null,
|
||||
() -> {
|
||||
try {
|
||||
InputStreamReader isr = new InputStreamReader(proc.getInputStream());
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (print) {
|
||||
System.out.println("[xpiped] " + line);
|
||||
}
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
},
|
||||
"daemon sysout");
|
||||
out.setDaemon(true);
|
||||
out.start();
|
||||
|
||||
var err = new Thread(
|
||||
null,
|
||||
() -> {
|
||||
try {
|
||||
InputStreamReader isr = new InputStreamReader(proc.getErrorStream());
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (print) {
|
||||
System.err.println("[xpiped] " + line);
|
||||
}
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
},
|
||||
"daemon syserr");
|
||||
err.setDaemon(true);
|
||||
err.start();
|
||||
}
|
||||
|
||||
public static boolean tryStop(BeaconClient client) throws Exception {
|
||||
DaemonStopExchange.Response res =
|
||||
client.performRequest(DaemonStopExchange.Request.builder().build());
|
||||
return res.isSuccess();
|
||||
}
|
||||
|
||||
public static String getDaemonDebugExecutable(String installationBase) {
|
||||
var osType = OsType.getLocal();
|
||||
var debug = BeaconConfig.launchDaemonInDebugMode();
|
||||
if (!debug) {
|
||||
throw new IllegalStateException();
|
||||
} else {
|
||||
if (BeaconConfig.attachDebuggerToDaemon()) {
|
||||
return FilePath.of(installationBase, XPipeInstallation.getDaemonDebugAttachScriptPath(osType))
|
||||
.toString();
|
||||
} else {
|
||||
return FilePath.of(installationBase, XPipeInstallation.getDaemonDebugScriptPath(osType))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/handshake";
|
||||
public boolean requiresAuthentication() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresAuthentication() {
|
||||
return false;
|
||||
public String getPath() {
|
||||
return "/handshake";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
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.XPipeDaemonMode;
|
||||
import io.xpipe.core.XPipeInstallation;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BeaconDaemonController {
|
||||
|
||||
private static boolean alreadyStarted;
|
||||
|
||||
public static void start(XPipeDaemonMode mode) throws Exception {
|
||||
if (BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
|
||||
alreadyStarted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var custom = false;
|
||||
Process process;
|
||||
if ((process = BeaconServer.tryStartCustom()) != null) {
|
||||
custom = true;
|
||||
} else {
|
||||
var defaultBase = XPipeInstallation.getLocalDefaultInstallationBasePath();
|
||||
process = BeaconServer.start(defaultBase.toString(), mode);
|
||||
}
|
||||
|
||||
waitForStartup(process, custom);
|
||||
if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() throws Exception {
|
||||
if (alreadyStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
|
||||
return;
|
||||
}
|
||||
|
||||
var client = BeaconClient.establishConnection(
|
||||
BeaconConfig.getUsedPort(),
|
||||
BeaconClientInformation.Api.builder()
|
||||
.name("Beacon daemon controller")
|
||||
.build());
|
||||
if (!BeaconServer.tryStop(client)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
waitForShutdown();
|
||||
}
|
||||
|
||||
private static void waitForStartup(Process process, boolean custom) throws IOException {
|
||||
for (int i = 0; i < 160; i++) {
|
||||
// Breaks when using nohup & disown
|
||||
// if (process != null && !custom && !process.isAlive()) {
|
||||
// throw new IOException("Daemon start failed");
|
||||
// }
|
||||
|
||||
if (process != null && custom && !process.isAlive() && process.exitValue() != 0) {
|
||||
throw new IOException("Custom launch command failed");
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
var s = BeaconClient.tryEstablishConnection(
|
||||
BeaconConfig.getUsedPort(),
|
||||
BeaconClientInformation.Api.builder()
|
||||
.name("Beacon daemon controller")
|
||||
.build());
|
||||
if (s.isPresent()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Wait for daemon start up timed out");
|
||||
}
|
||||
|
||||
private static void waitForShutdown() {
|
||||
for (int i = 0; i < 40; i++) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
var r = BeaconServer.isReachable(BeaconConfig.getUsedPort());
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package io.xpipe.beacon.test;
|
||||
|
||||
import io.xpipe.core.ModuleLayerLoader;
|
||||
import io.xpipe.core.OsType;
|
||||
import io.xpipe.core.XPipeDaemonMode;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
public class BeaconDaemonExtensionTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
ModuleLayerLoader.loadAll(ModuleLayer.boot(), throwable -> throwable.printStackTrace());
|
||||
BeaconDaemonController.start(
|
||||
OsType.getLocal().equals(OsType.WINDOWS) ? XPipeDaemonMode.TRAY : XPipeDaemonMode.BACKGROUND);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void teardown() throws Exception {
|
||||
BeaconDaemonController.stop();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.Module;
|
||||
|
||||
open module io.xpipe.beacon {
|
||||
exports io.xpipe.beacon;
|
||||
exports io.xpipe.beacon.test;
|
||||
exports io.xpipe.beacon.api;
|
||||
|
||||
requires com.fasterxml.jackson.core;
|
||||
|
||||
Reference in New Issue
Block a user