From c83b62730777efe51812d1caa6669e4ad493e1fe Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 4 Jul 2024 12:08:58 +0000 Subject: [PATCH] api rework [stage] --- .../impl/ConnectionAddExchangeImpl.java | 32 ++- .../impl/ConnectionRefreshExchangeImpl.java | 23 ++ .../impl/ConnectionToggleExchangeImpl.java | 2 +- .../impl/DaemonVersionExchangeImpl.java | 3 + .../app/browser/BrowserTransferComp.java | 2 + .../io/xpipe/app/storage/DataStorage.java | 14 +- app/src/main/java/module-info.java | 1 + .../io/xpipe/app/resources/misc/api.md | 215 +++++++++++++++++- .../java/io/xpipe/beacon/BeaconInterface.java | 2 +- .../beacon/api/ConnectionAddExchange.java | 3 +- .../beacon/api/ConnectionRefreshExchange.java | 30 +++ .../beacon/api/ConnectionToggleExchange.java | 3 +- .../beacon/api/DaemonVersionExchange.java | 7 + beacon/src/main/java/module-info.java | 1 + dist/changelogs/10.1_incremental.md | 1 + openapi.yaml | 64 +++++- version | 2 +- 17 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java create mode 100644 beacon/src/main/java/io/xpipe/beacon/api/ConnectionRefreshExchange.java diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java index 185693c08..e5704ecad 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionAddExchangeImpl.java @@ -1,19 +1,39 @@ package io.xpipe.app.beacon.impl; import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; -import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.api.ConnectionAddExchange; - -import java.util.UUID; +import io.xpipe.core.util.ValidationException; public class ConnectionAddExchangeImpl extends ConnectionAddExchange { @Override - public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException { - var cat = msg.getCategory() != null ? msg.getCategory() : DataStorage.DEFAULT_CATEGORY_UUID; - var entry = DataStorage.get().addStoreEntryIfNotPresent(DataStoreEntry.createNew(UUID.randomUUID(), cat, msg.getName(), msg.getData())); + public Object handle(HttpExchange exchange, Request msg) throws Throwable { + var found = DataStorage.get().getStoreEntryIfPresent(msg.getData(), false); + if (found.isPresent()) { + return Response.builder().connection(found.get().getUuid()).build(); + } + + var entry = DataStoreEntry.createNew(msg.getName(), msg.getData()); + try { + DataStorage.get().addStoreEntryInProgress(entry); + if (msg.getValidate()) { + entry.validateOrThrow(); + } + } catch (Throwable ex) { + if (ex instanceof ValidationException) { + ErrorEvent.expected(ex); + } else if (ex instanceof StackOverflowError) { + // Cycles in connection graphs can fail hard but are expected + ErrorEvent.expected(ex); + } + throw ex; + } finally { + DataStorage.get().removeStoreEntryInProgress(entry); + } + DataStorage.get().addStoreEntryIfNotPresent(entry); return Response.builder().connection(entry.getUuid()).build(); } } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java new file mode 100644 index 000000000..dfd656d96 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java @@ -0,0 +1,23 @@ +package io.xpipe.app.beacon.impl; + +import com.sun.net.httpserver.HttpExchange; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.util.FixedHierarchyStore; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.ConnectionRefreshExchange; + +public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws Throwable { + var e = DataStorage.get() + .getStoreEntryIfPresent(msg.getConnection()) + .orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection())); + if (e.getStore() instanceof FixedHierarchyStore) { + DataStorage.get().refreshChildren(e, true); + } else { + e.validateOrThrow(); + } + return Response.builder().build(); + } +} diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java index f208fcc51..dbecdba4d 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionToggleExchangeImpl.java @@ -16,7 +16,7 @@ public class ConnectionToggleExchangeImpl extends ConnectionToggleExchange { if (!(e.getStore() instanceof SingletonSessionStore singletonSessionStore)) { throw new BeaconClientException("Not a toggleable connection"); } - if (msg.isState()) { + if (msg.getState()) { singletonSessionStore.startSessionIfNeeded(); } else { singletonSessionStore.stopSessionIfNeeded(); diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonVersionExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonVersionExchangeImpl.java index 0354d52cf..ad69b0479 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonVersionExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonVersionExchangeImpl.java @@ -2,6 +2,7 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppVersion; +import io.xpipe.app.util.LicenseProvider; import io.xpipe.beacon.api.DaemonVersionExchange; import com.sun.net.httpserver.HttpExchange; @@ -19,6 +20,7 @@ public class DaemonVersionExchangeImpl extends DaemonVersionExchange { + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.version") + ")"; var version = AppProperties.get().getVersion(); + var pro = LicenseProvider.get().hasPaidLicense(); return Response.builder() .version(version) .canonicalVersion(AppVersion.parse(version) @@ -26,6 +28,7 @@ public class DaemonVersionExchangeImpl extends DaemonVersionExchange { .orElse("?")) .buildVersion(AppProperties.get().getBuild()) .jvmVersion(jvmVersion) + .pro(pro) .build(); } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java index 07c375df8..e4bfb2a2d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java @@ -43,6 +43,7 @@ public class BrowserTransferComp extends SimpleComp { var background = new LabelComp(AppI18n.observable("transferDescription")) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline"))) + .apply(struc -> struc.get().setWrapText(true)) .visible(Bindings.isEmpty(syncItems)); var backgroundStack = new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); @@ -77,6 +78,7 @@ public class BrowserTransferComp extends SimpleComp { aBoolean -> aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles"))) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left"))) .apply(struc -> AppFont.medium(struc.get())) + .apply(struc -> struc.get().setWrapText(true)) .hide(Bindings.isEmpty(syncItems)); var downloadButton = new IconButtonComp("mdi2d-download", () -> { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 37e5410d6..2573e747c 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -16,6 +16,7 @@ import javafx.util.Pair; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import lombok.SneakyThrows; import java.nio.file.Files; import java.nio.file.Path; @@ -338,7 +339,12 @@ public abstract class DataStorage { listeners.forEach(storageListener -> storageListener.onStoreListUpdate()); } + @SneakyThrows public boolean refreshChildren(DataStoreEntry e) { + return refreshChildren(e,false); + } + + public boolean refreshChildren(DataStoreEntry e, boolean throwOnFail) throws Exception { if (!(e.getStore() instanceof FixedHierarchyStore)) { return false; } @@ -348,8 +354,12 @@ public abstract class DataStorage { try { newChildren = ((FixedHierarchyStore) (e.getStore())).listChildren(e).stream().filter(dataStoreEntryRef -> dataStoreEntryRef != null && dataStoreEntryRef.get() != null).toList(); } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).handle(); - return false; + if (throwOnFail) { + throw ex; + } else { + ErrorEvent.fromThrowable(ex).handle(); + return false; + } } finally { e.decrementBusyCounter(); } diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index 37962472b..d04990eff 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -139,6 +139,7 @@ open module io.xpipe.app { ConnectionBrowseExchangeImpl, ConnectionTerminalExchangeImpl, ConnectionToggleExchangeImpl, + ConnectionRefreshExchangeImpl, DaemonOpenExchangeImpl, DaemonFocusExchangeImpl, DaemonStatusExchangeImpl, diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/api.md b/app/src/main/resources/io/xpipe/app/resources/misc/api.md index 6201a65a2..d548fda10 100644 --- a/app/src/main/resources/io/xpipe/app/resources/misc/api.md +++ b/app/src/main/resources/io/xpipe/app/resources/misc/api.md @@ -1307,6 +1307,164 @@ curl -X POST http://localhost:21721/connection/toggle \ +## Refreshes state of a connection + + + +`POST /connection/refresh` + +Performs a refresh on the specified connection. + +This will update the connection state information and also any children if the connection type has any. + +> Body parameter + +```json +{ + "connection": "36ad9716-a209-4f7f-9814-078d3349280c" +} +``` + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[ConnectionRefreshRequest](#schemaconnectionrefreshrequest)|true|none| + +> Example responses + +> 400 Response + +```json +{ + "message": "string" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The request was successful. The connection state was updated.|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|[ServerErrorResponse](#schemaservererrorresponse)| + + + +
+ +Code samples + +```javascript +const inputBody = '{ + "connection": "36ad9716-a209-4f7f-9814-078d3349280c" +}'; +const headers = { + 'Content-Type':'application/json', + 'Accept':'application/json', + 'Authorization':'Bearer {access-token}' +}; + +fetch('http://localhost:21721/connection/refresh', +{ + method: 'POST', + body: inputBody, + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```python +import requests +headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer {access-token}' +} + +data = """ +{ + "connection": "36ad9716-a209-4f7f-9814-078d3349280c" +} +""" +r = requests.post('http://localhost:21721/connection/refresh', headers = headers, data = data) + +print(r.json()) + +``` + +```java +var uri = URI.create("http://localhost:21721/connection/refresh"); +var client = HttpClient.newHttpClient(); +var request = HttpRequest + .newBuilder() + .uri(uri) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Authorization", "Bearer {access-token}") + .POST(HttpRequest.BodyPublishers.ofString(""" +{ + "connection": "36ad9716-a209-4f7f-9814-078d3349280c" +} + """)) + .build(); +var response = client.send(request, HttpResponse.BodyHandlers.ofString()); +System.out.println(response.statusCode()); +System.out.println(response.body()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Content-Type": []string{"application/json"}, + "Accept": []string{"application/json"}, + "Authorization": []string{"Bearer {access-token}"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("POST", "http://localhost:21721/connection/refresh", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +```shell +# You can also use wget +curl -X POST http://localhost:21721/connection/refresh \ + -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \ + --data ' +{ + "connection": "36ad9716-a209-4f7f-9814-078d3349280c" +} +' + +``` + +
+ ## Start shell connection @@ -1334,11 +1492,14 @@ These errors will be returned with the HTTP return code 500. > Example responses -> 400 Response +> 200 Response ```json { - "message": "string" + "shellDialect": 0, + "osType": "string", + "osName": "string", + "temp": "string" } ``` @@ -1346,7 +1507,7 @@ These errors will be returned with the HTTP return code 500. |Status|Meaning|Description|Schema| |---|---|---|---| -|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The shell session was started.|None| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The shell session was started.|[ShellStartResponse](#schemashellstartresponse)| |400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|[ClientErrorResponse](#schemaclienterrorresponse)| |401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None| |403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None| @@ -2621,6 +2782,32 @@ undefined |---|---|---|---|---| |connection|string|true|none|The connection uuid| +

ShellStartResponse

+ + + + + + +```json +{ + "shellDialect": 0, + "osType": "string", + "osName": "string", + "temp": "string" +} + +``` + +

Properties

+ +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|shellDialect|integer|true|none|The shell dialect| +|osType|string|true|none|The general type of operating system| +|osName|string|true|none|The display name of the operating system| +|temp|string|true|none|The location of the temporary directory| +

ShellStopRequest

@@ -2917,6 +3104,26 @@ undefined |usageCategory|desktop| |usageCategory|group| +

ConnectionRefreshRequest

+ + + + + + +```json +{ + "connection": "string" +} + +``` + +

Properties

+ +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|connection|string|true|none|The connection uuid| +

ConnectionAddRequest

@@ -2927,7 +3134,6 @@ undefined ```json { "name": "string", - "category": "string", "data": {} } @@ -2938,7 +3144,6 @@ undefined |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| |name|string|true|none|The connection name| -|category|string|false|none|The optional category uuid if you want to add the connection to a certain one. Otherwise the currently selected category will be used.| |data|object|true|none|The raw connection store data. Schemas for connection types are not documented but you can find the connection data of your existing connections in the xpipe vault.|

ConnectionAddResponse

diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java b/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java index 8b883a7f1..0e28a2f03 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconInterface.java @@ -72,7 +72,7 @@ public abstract class BeaconInterface { public abstract String getPath(); - public Object handle(HttpExchange exchange, T body) throws Exception { + public Object handle(HttpExchange exchange, T body) throws Throwable { throw new UnsupportedOperationException(); } diff --git a/beacon/src/main/java/io/xpipe/beacon/api/ConnectionAddExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/ConnectionAddExchange.java index c258ad93a..64ab54cd8 100644 --- a/beacon/src/main/java/io/xpipe/beacon/api/ConnectionAddExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/api/ConnectionAddExchange.java @@ -26,7 +26,8 @@ public class ConnectionAddExchange extends BeaconInterface { + + @Override + public String getPath() { + return "/connection/refresh"; + } + + @Jacksonized + @Builder + @Value + public static class Request { + @NonNull + UUID connection; + } + + @Jacksonized + @Builder + @Value + public static class Response {} +} diff --git a/beacon/src/main/java/io/xpipe/beacon/api/ConnectionToggleExchange.java b/beacon/src/main/java/io/xpipe/beacon/api/ConnectionToggleExchange.java index bbaa2c4d6..5a1bc1829 100644 --- a/beacon/src/main/java/io/xpipe/beacon/api/ConnectionToggleExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/api/ConnectionToggleExchange.java @@ -22,7 +22,8 @@ public class ConnectionToggleExchange extends BeaconInterface