Squash merge branch 18-release into master

This commit is contained in:
crschnick
2025-08-15 02:34:05 +00:00
parent 34a08e0877
commit 9d6aa2d340
750 changed files with 12026 additions and 7442 deletions
@@ -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();
}
}
-1
View File
@@ -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;