More beacon rework

This commit is contained in:
crschnick
2024-06-02 18:00:15 +00:00
parent 4f1009aad7
commit a5e2f4138e
35 changed files with 544 additions and 75 deletions
@@ -6,12 +6,15 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.XPipeInstallation;
import lombok.Getter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
public class AppBeaconServer {
@@ -25,6 +28,10 @@ public class AppBeaconServer {
private HttpServer server;
@Getter
private final Set<BeaconSession> sessions = new HashSet<>();
@Getter
private final Set<BeaconShellSession> shellSessions = new HashSet<>();
@Getter
private String localAuthSecret;
static {
int port;
@@ -46,6 +53,7 @@ public class AppBeaconServer {
public static void init() {
try {
INSTANCE.initAuthSecret();
INSTANCE.start();
TrackEvent.withInfo("Started http server")
.tag("port", INSTANCE.getPort())
@@ -85,6 +93,13 @@ public class AppBeaconServer {
server.stop(1);
}
private void initAuthSecret() throws IOException {
var file = XPipeInstallation.getLocalBeaconAuthFile();
var id = UUID.randomUUID().toString();
Files.writeString(file, id);
localAuthSecret = id;
}
private void start() throws IOException {
server = HttpServer.create(new InetSocketAddress("localhost", port), 10);
BeaconInterface.getAll().forEach(beaconInterface -> {
@@ -0,0 +1,12 @@
package io.xpipe.app.beacon;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import lombok.Value;
@Value
public class BeaconShellSession {
DataStoreEntry entry;
ShellControl control;
}
@@ -4,11 +4,11 @@ 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 io.xpipe.beacon.api.DaemonFocusExchange;
import java.io.IOException;
public class FocusExchangeImpl extends FocusExchange {
public class DaemonFocusExchangeImpl extends DaemonFocusExchange {
@Override
@@ -5,11 +5,11 @@ 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 io.xpipe.beacon.api.DaemonModeExchange;
import java.io.IOException;
public class ModeExchangeImpl extends ModeExchange {
public class DaemonModeExchangeImpl extends DaemonModeExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
// Wait for startup
@@ -29,7 +29,7 @@ public class ModeExchangeImpl extends ModeExchange {
}
OperationMode.switchToSyncIfPossible(mode);
return ModeExchange.Response.builder()
return DaemonModeExchange.Response.builder()
.usedMode(OperationMode.map(OperationMode.get()))
.build();
}
@@ -6,11 +6,11 @@ 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 io.xpipe.beacon.api.DaemonOpenExchange;
import java.io.IOException;
public class OpenExchangeImpl extends OpenExchange {
public class DaemonOpenExchangeImpl extends DaemonOpenExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
if (msg.getArguments().isEmpty()) {
@@ -5,11 +5,11 @@ 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 io.xpipe.beacon.api.DaemonStatusExchange;
import java.io.IOException;
public class StatusExchangeImpl extends StatusExchange {
public class DaemonStatusExchangeImpl extends DaemonStatusExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
@@ -5,11 +5,11 @@ 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 io.xpipe.beacon.api.DaemonStopExchange;
import java.io.IOException;
public class StopExchangeImpl extends StopExchange {
public class DaemonStopExchangeImpl extends DaemonStopExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
@@ -4,11 +4,11 @@ import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.core.AppProperties;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.VersionExchange;
import io.xpipe.beacon.api.DaemonVersionExchange;
import java.io.IOException;
public class VersionExchangeImpl extends VersionExchange {
public class DaemonVersionExchangeImpl extends DaemonVersionExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
@@ -4,6 +4,8 @@ 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.app.prefs.AppPrefs;
import io.xpipe.beacon.BeaconAuthMethod;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.HandshakeExchange;
@@ -15,8 +17,26 @@ public class HandshakeExchangeImpl extends HandshakeExchange {
@Override
public Object handle(HttpExchange exchange, Request body) throws IOException, BeaconClientException, BeaconServerException {
if (!checkAuth(body.getAuth())) {
throw new BeaconClientException("Authentication failed");
}
var session = new BeaconSession(body.getClient(), UUID.randomUUID().toString());
AppBeaconServer.get().addSession(session);
return Response.builder().token(session.getToken()).build();
}
private boolean checkAuth(BeaconAuthMethod authMethod) {
if (authMethod instanceof BeaconAuthMethod.Local local) {
var c = local.getAuthFileContent().trim();
return AppBeaconServer.get().getLocalAuthSecret().equals(c);
}
if (authMethod instanceof BeaconAuthMethod.ApiKey key) {
var c = key.getKey().trim();
return AppPrefs.get().apiKey().get().equals(c);
}
return false;
}
}
@@ -0,0 +1,60 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.QueryExchange;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class QueryExchangeImpl extends QueryExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var catMatcher = Pattern.compile(toRegex(msg.getCategoryFilter()));
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter()));
List<DataStoreEntry> found = new ArrayList<>();
for (DataStoreEntry storeEntry : DataStorage.get().getStoreEntries()) {
if (!storeEntry.getValidity().isUsable()) {
continue;
}
var name = DataStorage.get().getStorePath(storeEntry).toString();
if (!conMatcher.matcher(name).matches()) {
continue;
}
var cat = DataStorage.get().getStoreCategoryIfPresent(storeEntry.getCategoryUuid()).orElse(null);
if (cat == null) {
continue;
}
var c = DataStorage.get().getStorePath(cat).toString();
if (!catMatcher.matcher(c).matches()) {
continue;
}
found.add(storeEntry);
}
var mapped = new ArrayList<QueryResponse>();
for (DataStoreEntry e : found) {
var cat = DataStorage.get().getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow());
var obj = QueryExchange.QueryResponse.builder()
.uuid(e.getUuid()).category(cat).connection(DataStorage.get()
.getStorePath(e)).type(e.getProvider().getId()).build();
mapped.add(obj);
}
return Response.builder().found(mapped).build();
}
private String toRegex(String pattern) {
return pattern.replaceAll("\\*","[^\\\\]*?");
}
}
@@ -0,0 +1,35 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellExecExchange;
import lombok.SneakyThrows;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
public class ShellExecExchangeImpl extends ShellExecExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
if (existing.isEmpty()) {
throw new BeaconClientException("No shell session active for connection");
}
AtomicReference<String> out = new AtomicReference<>();
AtomicReference<String> err = new AtomicReference<>();
long exitCode;
try (var command = existing.get().getControl().command(msg.getCommand()).start()) {
command.accumulateStdout(s -> out.set(s));
command.accumulateStderr(s -> err.set(s));
exitCode = command.getExitCode();
}
return Response.builder().stdout(out.get()).stderr(err.get()).exitCode(exitCode).build();
}
}
@@ -0,0 +1,34 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.beacon.BeaconShellSession;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellStartExchange;
import io.xpipe.core.store.ShellStore;
import lombok.SneakyThrows;
import java.io.IOException;
public class ShellStartExchangeImpl extends ShellStartExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
if (!(e.getStore() instanceof ShellStore s)) {
throw new BeaconClientException("Not a shell connection");
}
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
if (existing.isPresent()) {
return Response.builder().build();
}
var control = s.control().start();
AppBeaconServer.get().getShellSessions().add(new BeaconShellSession(e, control));
return Response.builder().build();
}
}
@@ -0,0 +1,26 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.beacon.api.ShellStopExchange;
import lombok.SneakyThrows;
import java.io.IOException;
public class ShellStopExchangeImpl extends ShellStopExchange {
@Override
@SneakyThrows
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
var e = DataStorage.get().getStoreEntryIfPresent(msg.getConnection()).orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
var existing = AppBeaconServer.get().getShellSessions().stream().filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)).findFirst();
if (existing.isPresent()) {
existing.get().getControl().close();
AppBeaconServer.get().getShellSessions().remove(existing.get());
}
return Response.builder().build();
}
}
@@ -23,7 +23,7 @@ public abstract class BrowserSessionTab<T extends DataStore> {
this.browserModel = browserModel;
this.entry = entry;
this.name = DataStorage.get().getStoreDisplayName(entry.get());
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
this.tooltip = DataStorage.get().getStorePath(entry.getEntry()).toString();
}
public abstract Comp<?> comp();
@@ -12,8 +12,8 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper;
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.beacon.api.DaemonFocusExchange;
import io.xpipe.beacon.api.DaemonOpenExchange;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation;
@@ -85,16 +85,16 @@ public class LauncherCommand implements Callable<Integer> {
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());
client.get().performRequest(DaemonFocusExchange.Request.builder().mode(getEffectiveMode()).build());
if (!inputs.isEmpty()) {
client.get().performRequest(
OpenExchange.Request.builder().arguments(inputs).build());
DaemonOpenExchange.Request.builder().arguments(inputs).build());
}
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
try {
client.get().performRequest(OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
client.get().performRequest(DaemonOpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle();
}
@@ -122,11 +122,17 @@ public class AppPrefs {
final Property<Integer> httpServerPort =
map(new SimpleObjectProperty<>(XPipeInstallation.getDefaultBeaconPort()), "httpServerPort", Integer.class);
final StringProperty apiKey =
map(new SimpleStringProperty(UUID.randomUUID().toString()), "apiKey", String.class);
public ObservableValue<Integer> httpServerPort() {
return httpServerPort;
}
public ObservableStringValue apiKey() {
return apiKey;
}
private final IntegerProperty editorReloadTimeout =
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
private final BooleanProperty confirmDeletions =
@@ -5,10 +5,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.FixedHierarchyStore;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.DataStoreId;
import io.xpipe.core.store.FixedChildStore;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.*;
import io.xpipe.core.util.UuidHelper;
import javafx.util.Pair;
import lombok.Getter;
@@ -731,6 +728,22 @@ public abstract class DataStorage {
return children;
}
public List<DataStoreCategory> getCategoryParentHierarchy(DataStoreCategory cat) {
var es = new ArrayList<DataStoreCategory>();
es.add(cat);
DataStoreCategory current = cat;
while ((current = getStoreCategoryIfPresent(current.getParentCategory()).orElse(null)) != null) {
if (es.contains(current)) {
break;
}
es.addFirst(current);
}
return es;
}
public List<DataStoreEntry> getStoreParentHierarchy(DataStoreEntry entry) {
var es = new ArrayList<DataStoreEntry>();
es.add(entry);
@@ -747,34 +760,17 @@ public abstract class DataStorage {
return es;
}
public DataStoreId getId(DataStoreEntry entry) {
return DataStoreId.create(getStoreParentHierarchy(entry).stream()
public StorePath getStorePath(DataStoreEntry entry) {
return StorePath.create(getStoreParentHierarchy(entry).stream()
.filter(e -> !(e.getStore() instanceof LocalStore))
.map(e -> e.getName().replaceAll(":", "_"))
.map(e -> e.getName().replaceAll("/", "_"))
.toArray(String[]::new));
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStoreId id) {
var current = getStoreEntryIfPresent(id.getNames().getFirst());
if (current.isPresent()) {
for (int i = 1; i < id.getNames().size(); i++) {
var children = getStoreChildren(current.get());
int finalI = i;
current = children.stream()
.filter(dataStoreEntry -> dataStoreEntry
.getName()
.equalsIgnoreCase(id.getNames().get(finalI)))
.findFirst();
if (current.isEmpty()) {
break;
}
}
if (current.isPresent()) {
return current;
}
}
return Optional.empty();
public StorePath getStorePath(DataStoreCategory entry) {
return StorePath.create(getCategoryParentHierarchy(entry).stream()
.map(e -> e.getName().replaceAll("/", "_"))
.toArray(String[]::new));
}
public Optional<DataStoreEntry> getStoreEntryInProgressIfPresent(@NonNull DataStore store) {
+3 -9
View File
@@ -127,15 +127,9 @@ open module io.xpipe.app {
AppLogs.Slf4jProvider;
provides EventHandler with
EventHandlerImpl;
provides BeaconInterface with
OpenExchangeImpl,
FocusExchangeImpl,
StatusExchangeImpl,
StopExchangeImpl,
HandshakeExchangeImpl,
ModeExchangeImpl,
provides BeaconInterface with ShellStartExchangeImpl, ShellStopExchangeImpl, ShellExecExchangeImpl, QueryExchangeImpl, DaemonOpenExchangeImpl, DaemonFocusExchangeImpl, DaemonStatusExchangeImpl, DaemonStopExchangeImpl,
HandshakeExchangeImpl, DaemonModeExchangeImpl,
AskpassExchangeImpl,
TerminalWaitExchangeImpl,
TerminalLaunchExchangeImpl,
VersionExchangeImpl;
TerminalLaunchExchangeImpl, DaemonVersionExchangeImpl;
}
@@ -0,0 +1,34 @@
package io.xpipe.beacon;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
public interface BeaconAuthMethod {
@JsonTypeName("local")
@Value
@Builder
@Jacksonized
public static class Local implements BeaconAuthMethod {
@NonNull
String authFileContent;
}
@JsonTypeName("apiKey")
@Value
@Builder
@Jacksonized
public static class ApiKey implements BeaconAuthMethod {
@NonNull
String key;
}
}
@@ -1,6 +1,6 @@
package io.xpipe.beacon;
import io.xpipe.beacon.api.StopExchange;
import io.xpipe.beacon.api.DaemonStopExchange;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.util.XPipeDaemonMode;
@@ -108,7 +108,7 @@ public class BeaconServer {
}
public static boolean tryStop(BeaconClient client) throws Exception {
StopExchange.Response res = client.performRequest(StopExchange.Request.builder().build());
DaemonStopExchange.Response res = client.performRequest(DaemonStopExchange.Request.builder().build());
return res.isSuccess();
}
@@ -7,11 +7,11 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class FocusExchange extends BeaconInterface<FocusExchange.Request> {
public class DaemonFocusExchange extends BeaconInterface<DaemonFocusExchange.Request> {
@Override
public String getPath() {
return "/focus";
return "/daemon/focus";
}
@Jacksonized
@@ -7,11 +7,11 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ModeExchange extends BeaconInterface<ModeExchange.Request> {
public class DaemonModeExchange extends BeaconInterface<DaemonModeExchange.Request> {
@Override
public String getPath() {
return "/mode";
return "/daemon/mode";
}
@Jacksonized
@@ -8,11 +8,11 @@ import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class OpenExchange extends BeaconInterface<OpenExchange.Request> {
public class DaemonOpenExchange extends BeaconInterface<DaemonOpenExchange.Request> {
@Override
public String getPath() {
return "/open";
return "/daemon/open";
}
@Jacksonized
@@ -5,11 +5,11 @@ import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class StatusExchange extends BeaconInterface<StatusExchange.Request> {
public class DaemonStatusExchange extends BeaconInterface<DaemonStatusExchange.Request> {
@Override
public String getPath() {
return "/status";
return "/daemon/status";
}
@Value
@@ -8,11 +8,11 @@ import lombok.extern.jackson.Jacksonized;
/**
* Requests the daemon to stop.
*/
public class StopExchange extends BeaconInterface<StopExchange.Request> {
public class DaemonStopExchange extends BeaconInterface<DaemonStopExchange.Request> {
@Override
public String getPath() {
return "/stop";
return "/daemon/stop";
}
@Jacksonized
@@ -5,11 +5,11 @@ import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class VersionExchange extends BeaconInterface<VersionExchange.Request> {
public class DaemonVersionExchange extends BeaconInterface<DaemonVersionExchange.Request> {
@Override
public String getPath() {
return "/version";
return "/daemon/version";
}
@Jacksonized
@@ -1,8 +1,10 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconAuthMethod;
import io.xpipe.beacon.BeaconClientInformation;
import io.xpipe.beacon.BeaconInterface;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@@ -22,6 +24,9 @@ public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request
@Builder
@Value
public static class Request {
@NonNull
BeaconAuthMethod auth;
@NonNull
BeaconClientInformation client;
}
@@ -0,0 +1,48 @@
package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.store.StorePath;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.UUID;
public class QueryExchange extends BeaconInterface<QueryExchange.Request> {
@Override
public String getPath() {
return "/query";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
String categoryFilter;
@NonNull
String connectionFilter;
@NonNull
String typeFilter;
}
@Jacksonized
@Builder
@Value
public static class Response {
List<QueryResponse> found;
}
@Jacksonized
@Builder
@Value
public static class QueryResponse {
UUID uuid;
StorePath category;
StorePath connection;
String type;
}
}
@@ -0,0 +1,38 @@
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 ShellExecExchange extends BeaconInterface<ShellExecExchange.Request> {
@Override
public String getPath() {
return "/shell/exec";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
UUID connection;
@NonNull
String command;
}
@Jacksonized
@Builder
@Value
public static class Response {
long exitCode;
@NonNull
String stdout;
@NonNull
String stderr;
}
}
@@ -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 ShellStartExchange extends BeaconInterface<ShellStartExchange.Request> {
@Override
public String getPath() {
return "/shell/start";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
UUID connection;
}
@Jacksonized
@Builder
@Value
public static class Response {}
}
@@ -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 ShellStopExchange extends BeaconInterface<ShellStopExchange.Request> {
@Override
public String getPath() {
return "/shell/stop";
}
@Jacksonized
@Builder
@Value
public static class Request {
@NonNull
UUID connection;
}
@Jacksonized
@Builder
@Value
public static class Response {}
}
+3 -3
View File
@@ -24,9 +24,9 @@ open module io.xpipe.beacon {
BeaconInterface.Loader;
provides Module with
BeaconJacksonModule;
provides BeaconInterface with ModeExchange,StatusExchange, FocusExchange, OpenExchange, StopExchange, HandshakeExchange,
provides BeaconInterface with ShellStartExchange, ShellStopExchange, ShellExecExchange, DaemonModeExchange, DaemonStatusExchange, DaemonFocusExchange, DaemonOpenExchange, DaemonStopExchange, HandshakeExchange,
QueryExchange,
AskpassExchange,
TerminalWaitExchange,
TerminalLaunchExchange,
VersionExchange;
TerminalLaunchExchange, DaemonVersionExchange;
}
@@ -1,6 +1,5 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -28,7 +27,6 @@ public class DataStoreId {
private final List<String> names;
@JsonCreator
public DataStoreId(List<String> names) {
this.names = names;
}
@@ -0,0 +1,84 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Represents a reference to an XPipe storage location.
* <p>
* To allow for a simple usage, the names are trimmed and
* converted to lower case names when creating them.
* The names are separated by a slash and are therefore not allowed to contain slashes themselves.
*
* @see #fromString(String)
*/
@EqualsAndHashCode
@Getter
public class StorePath {
public static final char SEPARATOR = '/';
private final List<String> names;
@JsonCreator
public StorePath(List<String> names) {
this.names = names;
}
/**
* Creates a new store path.
*
* @throws IllegalArgumentException if any name is not valid
*/
public static StorePath create(String... names) {
if (names == null) {
throw new IllegalArgumentException("Names are null");
}
if (Arrays.stream(names).anyMatch(s -> s == null)) {
throw new IllegalArgumentException("Name is null");
}
if (Arrays.stream(names).anyMatch(s -> s.contains("" + SEPARATOR))) {
throw new IllegalArgumentException("Separator character " + SEPARATOR + " is not allowed in the names");
}
if (Arrays.stream(names).anyMatch(s -> s.trim().length() == 0)) {
throw new IllegalArgumentException("Trimmed entry name is empty");
}
return new StorePath(Arrays.stream(names).toList());
}
/**
* Creates a new store path from a string representation.
*
* @param s the string representation, must be not null and fulfill certain requirements
* @throws IllegalArgumentException if the string is not valid
*/
public static StorePath fromString(String s) {
if (s == null) {
throw new IllegalArgumentException("String is null");
}
var split = s.split(String.valueOf(SEPARATOR), -1);
var names =
Arrays.stream(split).map(String::trim).map(String::toLowerCase).toList();
if (names.stream().anyMatch(s1 -> s1.isEmpty())) {
throw new IllegalArgumentException("Name must not be empty");
}
return new StorePath(names);
}
@Override
public String toString() {
return names.stream().map(String::toLowerCase).collect(Collectors.joining("" + SEPARATOR));
}
}
@@ -30,6 +30,10 @@ public class XPipeInstallation {
return isStaging() ? "io.xpipe.xpipe-ptb" : "io.xpipe.xpipe";
}
public static Path getLocalBeaconAuthFile() {
return Path.of(System.getProperty("java.io.tmpdir"), "xpipe_auth");
}
public static String createExternalAsyncLaunchCommand(
String installationBase, XPipeDaemonMode mode, String arguments, boolean restart) {
var suffix = (arguments != null ? " " + arguments : "");