mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-09-28 19:35:41 +00:00
More beacon rework
This commit is contained in:
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
+2
-2
@@ -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()) {
|
||||
+2
-2
@@ -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 {
|
||||
+2
-2
@@ -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 {
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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 {}
|
||||
}
|
||||
@@ -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 : "");
|
||||
|
||||
Reference in New Issue
Block a user