From 9440037f03220d61a8421e35b602021cb0fcd29e Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Fri, 17 Jun 2022 05:24:09 +0200 Subject: [PATCH] Rework exchanges, rework stores, add dialog API --- ...ExecuteExchange.java => EditExchange.java} | 14 +- .../exchange/EditPreparationExchange.java | 36 -- .../exchange/QueryDataSourceExchange.java | 10 +- .../exchange/ReadPreparationExchange.java | 4 +- .../beacon/exchange/StoreStreamExchange.java | 4 +- .../beacon/exchange/cli/ConvertExchange.java | 4 +- .../exchange/cli/ListStoresExchange.java | 33 ++ .../{ => cli}/RemoveCollectionExchange.java | 3 +- .../{ => cli}/RemoveEntryExchange.java | 3 +- .../exchange/cli/RemoveStoreExchange.java | 33 ++ .../{ => cli}/RenameCollectionExchange.java | 3 +- .../{ => cli}/RenameEntryExchange.java | 3 +- .../exchange/cli/RenameStoreExchange.java | 33 ++ .../beacon/exchange/cli/StoreAddExchange.java | 14 +- .../cli/WritePreparationExchange.java | 4 +- .../beacon/exchange/data/StoreListEntry.java | 14 + beacon/src/main/java/module-info.java | 6 +- .../io/xpipe/core/connection/Connection.java | 4 - .../xpipe/core/dialog/BaseQueryElement.java | 10 +- .../io/xpipe/core/dialog/BusyElement.java | 12 + .../java/io/xpipe/core/dialog/Choice.java | 15 + .../io/xpipe/core/dialog/ChoiceElement.java | 25 +- .../java/io/xpipe/core/dialog/Dialog.java | 316 ++++++++++++++---- .../io/xpipe/core/dialog/DialogReference.java | 18 + .../io/xpipe/core/dialog/QueryConverter.java | 53 ++- .../io/xpipe/core/dialog/QueryElement.java | 11 +- .../core/store/CollectionEntryDataStore.java | 2 +- .../xpipe/core/store/CommandInputStore.java | 19 -- .../java/io/xpipe/core/store/DataStore.java | 11 + .../java/io/xpipe/core/store/FileStore.java | 44 +++ ...{FileDataStore.java => FilenameStore.java} | 7 +- .../io/xpipe/core/store/HttpRequestStore.java | 52 --- .../xpipe/core/store/LocalFileDataStore.java | 70 ---- .../xpipe/core/store/LocalMachineStore.java | 29 ++ .../io/xpipe/core/store/MachineStore.java | 16 + .../xpipe/core/store/RemoteFileDataStore.java | 34 -- .../io/xpipe/core/store/StreamDataStore.java | 22 -- .../io/xpipe/core/util/CoreJacksonModule.java | 36 +- .../main/java/io/xpipe/core/util/Secret.java | 30 ++ .../xpipe/extension/DataSourceProvider.java | 75 ++--- .../xpipe/extension/DataSourceProviders.java | 24 +- .../io/xpipe/extension/DataStoreProvider.java | 13 +- .../xpipe/extension/DataStoreProviders.java | 8 + .../SimpleFileDataSourceProvider.java | 4 +- .../extension/UniformDataSourceProvider.java | 6 +- .../extension/prefs/PrefsChoiceValue.java | 4 + 46 files changed, 737 insertions(+), 454 deletions(-) rename beacon/src/main/java/io/xpipe/beacon/exchange/{EditExecuteExchange.java => EditExchange.java} (75%) delete mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/EditPreparationExchange.java create mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java rename beacon/src/main/java/io/xpipe/beacon/exchange/{ => cli}/RemoveCollectionExchange.java (87%) rename beacon/src/main/java/io/xpipe/beacon/exchange/{ => cli}/RemoveEntryExchange.java (89%) create mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java rename beacon/src/main/java/io/xpipe/beacon/exchange/{ => cli}/RenameCollectionExchange.java (88%) rename beacon/src/main/java/io/xpipe/beacon/exchange/{ => cli}/RenameEntryExchange.java (90%) create mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java create mode 100644 beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java delete mode 100644 core/src/main/java/io/xpipe/core/connection/Connection.java create mode 100644 core/src/main/java/io/xpipe/core/dialog/BusyElement.java create mode 100644 core/src/main/java/io/xpipe/core/dialog/Choice.java create mode 100644 core/src/main/java/io/xpipe/core/dialog/DialogReference.java delete mode 100644 core/src/main/java/io/xpipe/core/store/CommandInputStore.java create mode 100644 core/src/main/java/io/xpipe/core/store/FileStore.java rename core/src/main/java/io/xpipe/core/store/{FileDataStore.java => FilenameStore.java} (68%) delete mode 100644 core/src/main/java/io/xpipe/core/store/HttpRequestStore.java delete mode 100644 core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java create mode 100644 core/src/main/java/io/xpipe/core/store/LocalMachineStore.java create mode 100644 core/src/main/java/io/xpipe/core/store/MachineStore.java delete mode 100644 core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java create mode 100644 core/src/main/java/io/xpipe/core/util/Secret.java diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/EditExecuteExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/EditExchange.java similarity index 75% rename from beacon/src/main/java/io/xpipe/beacon/exchange/EditExecuteExchange.java rename to beacon/src/main/java/io/xpipe/beacon/exchange/EditExchange.java index d5ee2867b..e11fcd226 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/EditExecuteExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/EditExchange.java @@ -2,7 +2,7 @@ package io.xpipe.beacon.exchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.DataSourceConfigInstance; +import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceReference; import lombok.Builder; @@ -11,13 +11,13 @@ import lombok.Value; import lombok.extern.jackson.Jacksonized; /** - * Performs an edit for a data source. + * Requests to edit a data source. */ -public class EditExecuteExchange implements MessageExchange { +public class EditExchange implements MessageExchange { @Override public String getId() { - return "editExecute"; + return "edit"; } @Jacksonized @@ -26,15 +26,15 @@ public class EditExecuteExchange implements MessageExchange { public static class Request implements RequestMessage { @NonNull DataSourceReference ref; - - @NonNull - DataSourceConfigInstance config; } @Jacksonized @Builder @Value public static class Response implements ResponseMessage { + @NonNull + DialogReference config; + DataSourceId id; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/EditPreparationExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/EditPreparationExchange.java deleted file mode 100644 index e4d796354..000000000 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/EditPreparationExchange.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.xpipe.beacon.exchange; - -import io.xpipe.beacon.message.RequestMessage; -import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.DataSourceConfigInstance; -import io.xpipe.core.source.DataSourceReference; -import lombok.Builder; -import lombok.NonNull; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -/** - * Requests to edit a data source. - */ -public class EditPreparationExchange implements MessageExchange { - - @Override - public String getId() { - return "editPreparation"; - } - - @Jacksonized - @Builder - @Value - public static class Request implements RequestMessage { - @NonNull - DataSourceReference ref; - } - - @Jacksonized - @Builder - @Value - public static class Response implements ResponseMessage { - DataSourceConfigInstance config; - } -} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/QueryDataSourceExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/QueryDataSourceExchange.java index af4121b12..991ec94a4 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/QueryDataSourceExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/QueryDataSourceExchange.java @@ -2,13 +2,17 @@ package io.xpipe.beacon.exchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.*; +import io.xpipe.core.source.DataSourceId; +import io.xpipe.core.source.DataSourceInfo; +import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.store.DataStore; import lombok.Builder; import lombok.NonNull; import lombok.Value; import lombok.extern.jackson.Jacksonized; +import java.util.Map; + /** * Queries general information about a data source. */ @@ -38,6 +42,8 @@ public class QueryDataSourceExchange implements MessageExchange { @NonNull DataStore store; @NonNull - DataSourceConfigInstance config; + String provider; + @NonNull + Map config; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/ReadPreparationExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/ReadPreparationExchange.java index 58fde9c85..270caa582 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/ReadPreparationExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/ReadPreparationExchange.java @@ -2,7 +2,7 @@ package io.xpipe.beacon.exchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.DataSourceConfigInstance; +import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.store.DataStore; import lombok.Builder; import lombok.NonNull; @@ -38,6 +38,6 @@ public class ReadPreparationExchange implements MessageExchange { String determinedType; @NonNull - DataSourceConfigInstance config; + DialogReference config; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java index 672941a8b..bc2cabe13 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/StoreStreamExchange.java @@ -2,7 +2,7 @@ package io.xpipe.beacon.exchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.store.StreamDataStore; +import io.xpipe.core.store.FileStore; import lombok.Builder; import lombok.Value; import lombok.extern.jackson.Jacksonized; @@ -27,6 +27,6 @@ public class StoreStreamExchange implements MessageExchange { @Builder @Value public static class Response implements ResponseMessage { - StreamDataStore store; + FileStore store; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java index 543763756..c59a97b6a 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ConvertExchange.java @@ -3,7 +3,7 @@ package io.xpipe.beacon.exchange.cli; import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.DataSourceConfigInstance; +import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceType; @@ -38,6 +38,6 @@ public class ConvertExchange implements MessageExchange { @Value public static class Response implements ResponseMessage { @NonNull - DataSourceConfigInstance config; + DialogReference config; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java new file mode 100644 index 000000000..ffa807605 --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/ListStoresExchange.java @@ -0,0 +1,33 @@ +package io.xpipe.beacon.exchange.cli; + +import io.xpipe.beacon.exchange.MessageExchange; +import io.xpipe.beacon.exchange.data.StoreListEntry; +import io.xpipe.beacon.message.RequestMessage; +import io.xpipe.beacon.message.ResponseMessage; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +import java.util.List; + +public class ListStoresExchange implements MessageExchange { + + @Override + public String getId() { + return "listStores"; + } + + @Jacksonized + @Builder + @Value + public static class Request implements RequestMessage { + + } + + @Jacksonized + @Builder + @Value + public static class Response implements ResponseMessage { + List entries; + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/RemoveCollectionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java similarity index 87% rename from beacon/src/main/java/io/xpipe/beacon/exchange/RemoveCollectionExchange.java rename to beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java index d0732c5f8..3f4983c42 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/RemoveCollectionExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveCollectionExchange.java @@ -1,5 +1,6 @@ -package io.xpipe.beacon.exchange; +package io.xpipe.beacon.exchange.cli; +import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; import lombok.Builder; diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/RemoveEntryExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveEntryExchange.java similarity index 89% rename from beacon/src/main/java/io/xpipe/beacon/exchange/RemoveEntryExchange.java rename to beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveEntryExchange.java index 1c8972660..f6f655a9d 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/RemoveEntryExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveEntryExchange.java @@ -1,5 +1,6 @@ -package io.xpipe.beacon.exchange; +package io.xpipe.beacon.exchange.cli; +import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; import io.xpipe.core.source.DataSourceId; diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java new file mode 100644 index 000000000..b5aa793db --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RemoveStoreExchange.java @@ -0,0 +1,33 @@ +package io.xpipe.beacon.exchange.cli; + +import io.xpipe.beacon.exchange.MessageExchange; +import io.xpipe.beacon.message.RequestMessage; +import io.xpipe.beacon.message.ResponseMessage; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +public class RemoveStoreExchange implements MessageExchange { + + @Override + public String getId() { + return "removeStore"; + } + + @Jacksonized + @Builder + @Value + public static class Request implements RequestMessage { + @NonNull + String storeName; + + boolean removeUnderlying; + } + + @Jacksonized + @Builder + @Value + public static class Response implements ResponseMessage { + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/RenameCollectionExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java similarity index 88% rename from beacon/src/main/java/io/xpipe/beacon/exchange/RenameCollectionExchange.java rename to beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java index b5abf90bf..73e78ec00 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/RenameCollectionExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameCollectionExchange.java @@ -1,5 +1,6 @@ -package io.xpipe.beacon.exchange; +package io.xpipe.beacon.exchange.cli; +import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; import lombok.Builder; diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/RenameEntryExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameEntryExchange.java similarity index 90% rename from beacon/src/main/java/io/xpipe/beacon/exchange/RenameEntryExchange.java rename to beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameEntryExchange.java index 73f898df9..3715ab872 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/RenameEntryExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameEntryExchange.java @@ -1,5 +1,6 @@ -package io.xpipe.beacon.exchange; +package io.xpipe.beacon.exchange.cli; +import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; import io.xpipe.core.source.DataSourceId; diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java new file mode 100644 index 000000000..18a13138f --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/RenameStoreExchange.java @@ -0,0 +1,33 @@ +package io.xpipe.beacon.exchange.cli; + +import io.xpipe.beacon.exchange.MessageExchange; +import io.xpipe.beacon.message.RequestMessage; +import io.xpipe.beacon.message.ResponseMessage; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +public class RenameStoreExchange implements MessageExchange { + + @Override + public String getId() { + return "renameStore"; + } + + @Jacksonized + @Builder + @Value + public static class Request implements RequestMessage { + @NonNull + String storeName; + @NonNull + String newName; + } + + @Jacksonized + @Builder + @Value + public static class Response implements ResponseMessage { + } +} diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java index 43353f777..b74861841 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/StoreAddExchange.java @@ -3,14 +3,12 @@ package io.xpipe.beacon.exchange.cli; import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.dialog.DialogElement; +import io.xpipe.core.dialog.DialogReference; +import io.xpipe.core.store.DataStore; import lombok.Builder; -import lombok.NonNull; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import java.util.UUID; - public class StoreAddExchange implements MessageExchange { @Override @@ -22,15 +20,17 @@ public class StoreAddExchange implements MessageExchange { @Builder @Value public static class Request implements RequestMessage { - @NonNull String input; + DataStore storeInput; + + String type; + String name; } @Jacksonized @Builder @Value public static class Response implements ResponseMessage { - UUID dialogKey; - DialogElement dialogElement; + DialogReference config; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/WritePreparationExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/WritePreparationExchange.java index fede1de25..40007f458 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/cli/WritePreparationExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/cli/WritePreparationExchange.java @@ -3,7 +3,7 @@ package io.xpipe.beacon.exchange.cli; import io.xpipe.beacon.exchange.MessageExchange; import io.xpipe.beacon.message.RequestMessage; import io.xpipe.beacon.message.ResponseMessage; -import io.xpipe.core.source.DataSourceConfigInstance; +import io.xpipe.core.dialog.DialogReference; import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.store.StreamDataStore; import lombok.Builder; @@ -37,6 +37,6 @@ public class WritePreparationExchange implements MessageExchange { @Value public static class Response implements ResponseMessage { @NonNull - DataSourceConfigInstance config; + DialogReference config; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java b/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java new file mode 100644 index 000000000..5c4e8258b --- /dev/null +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/data/StoreListEntry.java @@ -0,0 +1,14 @@ +package io.xpipe.beacon.exchange.data; + +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@Jacksonized +@Builder +public class StoreListEntry { + + String name; + String type; +} diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index 8fb129e08..bf698dc05 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -29,6 +29,8 @@ module io.xpipe.beacon { ModeExchange, StatusExchange, StopExchange, + RenameStoreExchange, + RemoveStoreExchange, StoreAddExchange, WritePreparationExchange, WriteExecuteExchange, @@ -36,11 +38,11 @@ module io.xpipe.beacon { ReadPreparationExchange, QueryTextDataExchange, ReadExecuteExchange, + ListStoresExchange, DialogExchange, QueryDataSourceExchange, StoreStreamExchange, - EditPreparationExchange, - EditExecuteExchange, + EditExchange, RemoveEntryExchange, RemoveCollectionExchange, RenameCollectionExchange, diff --git a/core/src/main/java/io/xpipe/core/connection/Connection.java b/core/src/main/java/io/xpipe/core/connection/Connection.java deleted file mode 100644 index 0aef2e809..000000000 --- a/core/src/main/java/io/xpipe/core/connection/Connection.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.xpipe.core.connection; - -public class Connection { -} diff --git a/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java b/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java index ce5eab804..715703efb 100644 --- a/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java @@ -9,13 +9,21 @@ import lombok.Getter; public class BaseQueryElement extends DialogElement { private final String description; + private final boolean newLine; private final boolean required; + private final boolean hidden; protected String value; @JsonCreator - public BaseQueryElement(String description, boolean required, String value) { + public BaseQueryElement(String description, boolean newLine, boolean required, boolean hidden, String value) { this.description = description; + this.newLine = newLine; this.required = required; + this.hidden = hidden; this.value = value; } + + public boolean isNewLine() { + return newLine; + } } diff --git a/core/src/main/java/io/xpipe/core/dialog/BusyElement.java b/core/src/main/java/io/xpipe/core/dialog/BusyElement.java new file mode 100644 index 000000000..49a84bf0e --- /dev/null +++ b/core/src/main/java/io/xpipe/core/dialog/BusyElement.java @@ -0,0 +1,12 @@ +package io.xpipe.core.dialog; + +import com.fasterxml.jackson.annotation.JsonTypeName; + +@JsonTypeName("busy") +public class BusyElement extends DialogElement { + + @Override + public boolean apply(String value) { + return true; + } +} diff --git a/core/src/main/java/io/xpipe/core/dialog/Choice.java b/core/src/main/java/io/xpipe/core/dialog/Choice.java new file mode 100644 index 000000000..50999ab38 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/dialog/Choice.java @@ -0,0 +1,15 @@ +package io.xpipe.core.dialog; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@Builder +@Jacksonized +@AllArgsConstructor +public class Choice { + Character character; + String description; +} diff --git a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java index de8a8a0c1..166145ebc 100644 --- a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java @@ -2,17 +2,14 @@ package io.xpipe.core.dialog; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; import java.util.List; @JsonTypeName("choice") public class ChoiceElement extends DialogElement { - private final List elements; + private final String description; + private final List elements; private int selected; @@ -42,26 +39,22 @@ public class ChoiceElement extends DialogElement { return false; } - @Value - @Builder - @Jacksonized - @AllArgsConstructor - public static class Element { - Character character; - String description; - } - @JsonCreator - public ChoiceElement(List elements, int selected) { + public ChoiceElement(String description, List elements, int selected) { + this.description = description; this.elements = elements; this.selected = selected; } - public List getElements() { + public List getElements() { return elements; } public int getSelected() { return selected; } + + public String getDescription() { + return description; + } } diff --git a/core/src/main/java/io/xpipe/core/dialog/Dialog.java b/core/src/main/java/io/xpipe/core/dialog/Dialog.java index 7372c0afc..c777518e2 100644 --- a/core/src/main/java/io/xpipe/core/dialog/Dialog.java +++ b/core/src/main/java/io/xpipe/core/dialog/Dialog.java @@ -1,42 +1,112 @@ package io.xpipe.core.dialog; +import io.xpipe.core.util.Secret; + +import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; public abstract class Dialog { - private static class Sequence extends Dialog { - - private int index = 0; - private final DialogElement[] es; - - public Sequence(DialogElement... es) { - this.es = es; - } - - @Override - public DialogElement start() { - index = 0; - return es[0]; - } - - @Override - public DialogElement receive(String answer) { - if (es[index].apply(answer)) { - if (index == es.length - 1) { - complete(); - return null; - } else { - return es[++index]; - } + public static Dialog empty() { + return new Dialog() { + @Override + public DialogElement start() throws Exception { + return null; } - return es[index]; + @Override + protected DialogElement next(String answer) throws Exception { + return null; + } + }; + } + + public static class Choice extends Dialog { + + private final ChoiceElement element; + + private Choice(String description, List elements, int selected) { + this.element = new ChoiceElement(description, elements, selected); + } + + @Override + public DialogElement start() throws Exception { + return element; + } + + @Override + protected DialogElement next(String answer) throws Exception { + if (element.apply(answer)) { + return null; + } + + return element; + } + + private int getSelected() { + return element.getSelected(); } } - public static Dialog chain(DialogElement... es) { - return new Dialog.Sequence(es); + public static Dialog.Choice choice(String description, List elements, int selected) { + Dialog.Choice c = new Dialog.Choice(description, elements, selected); + c.evaluateTo(c::getSelected); + return c; + } + + @SafeVarargs + public static Dialog.Choice choice(String description, Function toString, T def, T... vals) { + var elements = Arrays.stream(vals).map(v -> new io.xpipe.core.dialog.Choice(null, toString.apply(v))).toList(); + var index = Arrays.asList(vals).indexOf(def); + var c = choice(description, elements, index); + c.evaluateTo(() -> vals[c.getSelected()]); + return c; + } + + public static class Query extends Dialog { + + private final QueryElement element; + + private Query(String description, boolean newLine, boolean required, Object value, QueryConverter converter, boolean hidden) { + this.element = new QueryElement(description, newLine, required,value, converter, hidden); + } + + @Override + public Optional> toValue() { + return Optional.of(new AbstractMap.SimpleEntry<>(element.getDescription(), element.getValue())); + } + + @Override + public DialogElement start() throws Exception { + return element; + } + + @Override + protected DialogElement next(String answer) throws Exception { + if (element.apply(answer)) { + return null; + } + + return element; + } + + private T getConvertedValue() { + return element.getConvertedValue(); + } + } + + public static Dialog.Query query(String description, boolean newLine, boolean required, Object value, QueryConverter converter) { + var q = new Dialog.Query(description, newLine, required, value, converter, false); + q.evaluateTo(q::getConvertedValue); + return q; + } + public static Dialog.Query querySecret(String description, boolean newLine, boolean required, Secret value) { + var q = new Dialog.Query(description, newLine, required, value, QueryConverter.SECRET, true); + q.evaluateTo(q::getConvertedValue); + return q; } public static Dialog chain(Dialog... ds) { @@ -45,50 +115,100 @@ public abstract class Dialog { private int current = 0; @Override - public DialogElement start() { + public DialogElement start() throws Exception { current = 0; + eval = null; return ds[0].start(); } @Override - public DialogElement receive(String answer) { + protected DialogElement next(String answer) throws Exception { DialogElement currentElement = ds[current].receive(answer); if (currentElement == null) { - ds[current].complete(); - if (current == ds.length - 1) { - complete(); - return null; - } else { - return ds[++current].start(); - } + DialogElement next = null; + while (current < ds.length - 1 && (next = ds[++current].start()) == null) { + + }; + return next; } return currentElement; } - }; + }.evaluateTo(ds[ds.length - 1]); } - public static Dialog repeatIf(Dialog d, Supplier shouldRepeat) { + public static Dialog repeatIf(Dialog d, Predicate shouldRepeat) { return new Dialog() { @Override - public DialogElement start() { + public DialogElement start() throws Exception { + eval = null; return d.start(); } @Override - public DialogElement receive(String answer) { + protected DialogElement next(String answer) throws Exception { var next = d.receive(answer); if (next == null) { - if (shouldRepeat.get()) { + if (shouldRepeat.test(d.getResult())) { return d.start(); } } return next; } - }.evaluateTo(d.onCompletion); + }.evaluateTo(d).onCompletion(d.completion); + } + + public static Dialog header(String msg) { + return of(new HeaderElement(msg)).evaluateTo(() -> msg); + } + + public static Dialog header(Supplier msg) { + final String[] msgEval = {null}; + return new Dialog() { + @Override + public DialogElement start() throws Exception { + msgEval[0] = msg.get(); + return new HeaderElement(msgEval[0]); + } + + @Override + protected DialogElement next(String answer) throws Exception { + return null; + } + }.evaluateTo(() -> msgEval[0]); + } + + + public static Dialog busy() { + return of(new BusyElement()); + } + + public static interface FailableSupplier { + + T get() throws Exception; + } + + public static Dialog lazy(FailableSupplier d) { + return new Dialog() { + + Dialog dialog; + + @Override + public DialogElement start() throws Exception { + eval = null; + dialog = d.get(); + evaluateTo(dialog); + return dialog.start(); + } + + @Override + protected DialogElement next(String answer) throws Exception { + return dialog.receive(answer); + } + }; } public static Dialog of(DialogElement e) { @@ -96,14 +216,14 @@ public abstract class Dialog { @Override - public DialogElement start() { + public DialogElement start() throws Exception { + eval = null; return e; } @Override - public DialogElement receive(String answer) { + protected DialogElement next(String answer) throws Exception { if (e.apply(answer)) { - complete(); return null; } @@ -112,18 +232,38 @@ public abstract class Dialog { }; } - public static Dialog retryIf(Dialog d, Supplier msg) { + + public static Dialog skipIf(Dialog d, Supplier check) { + return new Dialog() { + + private Dialog active; + + @Override + public DialogElement start() throws Exception { + active = check.get() ? null : d; + return active != null ? active.start() : null; + } + + @Override + protected DialogElement next(String answer) throws Exception { + return active != null ? active.receive(answer) : null; + } + }.evaluateTo(d).onCompletion(d.completion); + } + + public static Dialog retryIf(Dialog d, Function msg) { return new Dialog() { private boolean retry; @Override - public DialogElement start() { + public DialogElement start() throws Exception { + eval = null; return d.start(); } @Override - public DialogElement receive(String answer) { + protected DialogElement next(String answer) throws Exception { if (retry) { retry = false; return d.start(); @@ -131,7 +271,7 @@ public abstract class Dialog { var next = d.receive(answer); if (next == null) { - var s = msg.get(); + var s = msg.apply(d.getResult()); if (s != null) { retry = true; return new HeaderElement(s); @@ -140,47 +280,78 @@ public abstract class Dialog { return next; } - }.evaluateTo(d.onCompletion); + }.evaluateTo(d.evaluation).onCompletion(d.completion); } - public static Dialog choice(ChoiceElement choice, Function c) { + public static Dialog fork(String description, List elements, int selected, Function c) { + var choice = new ChoiceElement(description, elements, selected); return new Dialog() { private Dialog choiceMade; @Override - public DialogElement start() { + public DialogElement start() throws Exception { choiceMade = null; + eval = null; return choice; } @Override - public DialogElement receive(String answer) { + protected DialogElement next(String answer) throws Exception { if (choiceMade != null) { var r = choiceMade.receive(answer); - if (r == null) { - complete(); - } return r; } if (choice.apply(answer)) { choiceMade = c.apply(choice.getSelected()); - return choiceMade.start(); + return choiceMade != null ? choiceMade.start() : null; } return choice; } - }; + }.evaluateTo(() -> choice.getSelected()); } - private Object eval; - private Supplier onCompletion; + protected Object eval; + private Supplier evaluation; + private final List> completion = new ArrayList<>(); - public abstract DialogElement start(); + public abstract DialogElement start() throws Exception; + + public Optional> toValue() { + return Optional.empty(); + } + + public Dialog evaluateTo(Dialog d) { + evaluation = d.evaluation; + return this; + } public Dialog evaluateTo(Supplier s) { - onCompletion = s; + evaluation = s; + return this; + } + + @SuppressWarnings("unchecked") + public Dialog map(Function s) { + var oldEval = evaluation; + evaluation = () -> s.apply((T) oldEval.get()); + return this; + } + + public Dialog onCompletion(Consumer s) { + completion.add(s); + return this; + } + + public Dialog onCompletion(Runnable r) { + completion.add(v -> r.run()); + return this; + } + + public Dialog onCompletion(List> s) { + completion.addAll(s); return this; } @@ -189,11 +360,24 @@ public abstract class Dialog { return (T) eval; } - public void complete() { - if (onCompletion != null) { - eval = onCompletion.get(); + @SuppressWarnings("unchecked") + public void complete() { + if (evaluation != null) { + eval = evaluation.get(); + completion.forEach(c -> { + Consumer ct = (Consumer) c; + ct.accept((T) eval); + }); } } - public abstract DialogElement receive(String answer); + public final DialogElement receive(String answer) throws Exception { + var next = next(answer); + if (next == null) { + complete(); + } + return next; + } + + protected abstract DialogElement next(String answer) throws Exception; } diff --git a/core/src/main/java/io/xpipe/core/dialog/DialogReference.java b/core/src/main/java/io/xpipe/core/dialog/DialogReference.java new file mode 100644 index 000000000..02419afb6 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/dialog/DialogReference.java @@ -0,0 +1,18 @@ +package io.xpipe.core.dialog; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +import java.util.UUID; + +@Value +@Builder +@Jacksonized +@AllArgsConstructor +public class DialogReference { + + UUID dialogId; + DialogElement start; +} diff --git a/core/src/main/java/io/xpipe/core/dialog/QueryConverter.java b/core/src/main/java/io/xpipe/core/dialog/QueryConverter.java index f70567b78..cc5ac35c9 100644 --- a/core/src/main/java/io/xpipe/core/dialog/QueryConverter.java +++ b/core/src/main/java/io/xpipe/core/dialog/QueryConverter.java @@ -1,8 +1,12 @@ package io.xpipe.core.dialog; -import java.net.MalformedURLException; -import java.net.URL; +import io.xpipe.core.util.Secret; + +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.util.AbstractMap; +import java.util.Map; public abstract class QueryConverter { @@ -30,18 +34,51 @@ public abstract class QueryConverter { } }; - public static final QueryConverter URL = new QueryConverter() { + public static final QueryConverter SECRET = new QueryConverter() { @Override - protected URL fromString(String s) { + protected Secret fromString(String s) { + return Secret.parse(s); + } + + @Override + protected String toString(Secret value) { + return value.getValue(); + } + }; + + public static final QueryConverter> HTTP_HEADER = new QueryConverter>() { + @Override + protected Map.Entry fromString(String s) { + if (!s.contains(":")) { + throw new IllegalArgumentException("Missing colon"); + } + + var split = s.split(":"); + if (split.length != 2) { + throw new IllegalArgumentException("Too many colons"); + } + + return new AbstractMap.SimpleEntry<>(split[0].trim(), split[1].trim()); + } + + @Override + protected String toString(Map.Entry value) { + return value.getKey() + ": " + value.getValue(); + } + }; + + public static final QueryConverter URI = new QueryConverter() { + @Override + protected URI fromString(String s) { try { - return new URL(s); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); + return new URI(s); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage()); } } @Override - protected String toString(URL value) { + protected String toString(URI value) { return value.toString(); } }; diff --git a/core/src/main/java/io/xpipe/core/dialog/QueryElement.java b/core/src/main/java/io/xpipe/core/dialog/QueryElement.java index 439c71d6c..dec414cfe 100644 --- a/core/src/main/java/io/xpipe/core/dialog/QueryElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/QueryElement.java @@ -1,21 +1,14 @@ package io.xpipe.core.dialog; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @JsonSerialize(as = BaseQueryElement.class) public class QueryElement extends BaseQueryElement { - @JsonIgnore private final QueryConverter converter; - public QueryElement(String description, boolean required, String value, QueryConverter converter) { - super(description, required, value); - this.converter = converter; - } - - public QueryElement(String description, boolean required, Object value, QueryConverter converter) { - super(description, required, value != null ? value.toString() : null); + public QueryElement(String description, boolean newLine, boolean required, Object value, QueryConverter converter, boolean hidden) { + super(description, newLine, required, hidden, value != null ? value.toString() : null); this.converter = converter; } diff --git a/core/src/main/java/io/xpipe/core/store/CollectionEntryDataStore.java b/core/src/main/java/io/xpipe/core/store/CollectionEntryDataStore.java index 45f36252a..5fd740b12 100644 --- a/core/src/main/java/io/xpipe/core/store/CollectionEntryDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/CollectionEntryDataStore.java @@ -1,6 +1,6 @@ package io.xpipe.core.store; -public abstract class CollectionEntryDataStore implements FileDataStore { +public abstract class CollectionEntryDataStore implements StreamDataStore, FilenameStore { private final boolean directory; private final String name; diff --git a/core/src/main/java/io/xpipe/core/store/CommandInputStore.java b/core/src/main/java/io/xpipe/core/store/CommandInputStore.java deleted file mode 100644 index eb6851485..000000000 --- a/core/src/main/java/io/xpipe/core/store/CommandInputStore.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.xpipe.core.store; - -import com.fasterxml.jackson.annotation.JsonTypeName; -import lombok.Value; - -import java.io.InputStream; - -@Value -@JsonTypeName("commandInput") -public class CommandInputStore implements StreamDataStore { - - String cmd; - - @Override - public InputStream openInput() throws Exception { - var proc = Runtime.getRuntime().exec(cmd); - return proc.getInputStream(); - } -} diff --git a/core/src/main/java/io/xpipe/core/store/DataStore.java b/core/src/main/java/io/xpipe/core/store/DataStore.java index 173c77982..73fec67ca 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStore.java +++ b/core/src/main/java/io/xpipe/core/store/DataStore.java @@ -16,6 +16,17 @@ import java.util.Optional; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface DataStore { + default void validate() throws Exception { + } + + default boolean delete() throws Exception { + return false; + } + + default String toDisplay() { + return null; + } + /** * Casts this instance to the required type without checking whether a cast is possible. */ diff --git a/core/src/main/java/io/xpipe/core/store/FileStore.java b/core/src/main/java/io/xpipe/core/store/FileStore.java new file mode 100644 index 000000000..140fcb52f --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/FileStore.java @@ -0,0 +1,44 @@ +package io.xpipe.core.store; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.Value; + +import java.nio.file.Path; + +@Value +@JsonTypeName("file") +public class FileStore implements StreamDataStore, FilenameStore { + + public static FileStore local(Path p) { + return new FileStore(MachineStore.local(), p.toString()); + } + + public static FileStore local(String p) { + return new FileStore(MachineStore.local(), p); + } + + MachineStore machine; + String file; + + @JsonCreator + public FileStore(MachineStore machine, String file) { + this.machine = machine; + this.file = file; + } + + @Override + public String toDisplay() { + return file + "@" + machine.toDisplay(); + } + + @Override + public boolean persistent() { + return true; + } + + @Override + public String getFileName() { + return file; + } +} diff --git a/core/src/main/java/io/xpipe/core/store/FileDataStore.java b/core/src/main/java/io/xpipe/core/store/FilenameStore.java similarity index 68% rename from core/src/main/java/io/xpipe/core/store/FileDataStore.java rename to core/src/main/java/io/xpipe/core/store/FilenameStore.java index 5915b9289..0dd486cdb 100644 --- a/core/src/main/java/io/xpipe/core/store/FileDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/FilenameStore.java @@ -2,7 +2,7 @@ package io.xpipe.core.store; import java.util.Optional; -public interface FileDataStore extends StreamDataStore { +public interface FilenameStore extends DataStore { @Override default Optional determineDefaultName() { @@ -11,10 +11,5 @@ public interface FileDataStore extends StreamDataStore { return Optional.of(i != -1 ? n.substring(0, i) : n); } - @Override - default boolean persistent() { - return true; - } - String getFileName(); } diff --git a/core/src/main/java/io/xpipe/core/store/HttpRequestStore.java b/core/src/main/java/io/xpipe/core/store/HttpRequestStore.java deleted file mode 100644 index 0506d34d7..000000000 --- a/core/src/main/java/io/xpipe/core/store/HttpRequestStore.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.xpipe.core.store; - -import com.fasterxml.jackson.annotation.JsonTypeName; -import lombok.Value; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Map; -import java.util.Optional; - -@Value -@JsonTypeName("httpRequest") -public class HttpRequestStore implements StreamDataStore { - - public static boolean isHttpRequest(String s) { - return s.startsWith("http:") || s.startsWith("https:"); - } - - public static Optional fromString(String s) { - try { - var uri = new URI(s); - return Optional.of(new HttpRequestStore(uri, Map.of())); - } catch (URISyntaxException e) { - return Optional.empty(); - } - } - - URI uri; - Map headers; - - @Override - public InputStream openInput() throws Exception { - var b = HttpRequest.newBuilder().uri(uri); - headers.forEach(b::setHeader); - var req = b.GET().build(); - - var client = HttpClient.newHttpClient(); - var res = client.send(req, HttpResponse.BodyHandlers.ofByteArray()); - - return new ByteArrayInputStream(res.body()); - } - - @Override - public boolean exists() { - return false; - } -} diff --git a/core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java b/core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java deleted file mode 100644 index 11e9a6e46..000000000 --- a/core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.xpipe.core.store; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeName; -import lombok.EqualsAndHashCode; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.time.Instant; -import java.util.Optional; - -@JsonTypeName("local") -@EqualsAndHashCode -public class LocalFileDataStore implements FileDataStore { - - private final Path file; - - @JsonCreator - public LocalFileDataStore(Path file) { - this.file = file; - } - - public String toString() { - return getFileName(); - } - - @Override - public Optional determineLastModified() { - try { - var l = Files.getLastModifiedTime(file); - return Optional.of(l.toInstant()); - } catch (IOException e) { - return Optional.empty(); - } - } - - public Path getFile() { - return file; - } - - @Override - public InputStream openInput() throws Exception { - return new BufferedInputStream(Files.newInputStream(file)); - } - - @Override - public OutputStream openOutput() throws Exception { - return Files.newOutputStream(file); - } - - @Override - public OutputStream openAppendingOutput() throws Exception { - return Files.newOutputStream(file, StandardOpenOption.APPEND); - } - - @Override - public boolean exists() { - return Files.exists(file); - } - - @Override - public String getFileName() { - return file.getFileName().toString(); - } -} diff --git a/core/src/main/java/io/xpipe/core/store/LocalMachineStore.java b/core/src/main/java/io/xpipe/core/store/LocalMachineStore.java new file mode 100644 index 000000000..f39766a23 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/LocalMachineStore.java @@ -0,0 +1,29 @@ +package io.xpipe.core.store; + +import com.fasterxml.jackson.annotation.JsonTypeName; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +@JsonTypeName("local") +public class LocalMachineStore implements MachineStore { + + @Override + public String toDisplay() { + return "local"; + } + + @Override + public InputStream openInput(String file) throws Exception { + var p = Path.of(file); + return Files.newInputStream(p); + } + + @Override + public OutputStream openOutput(String file) throws Exception { + var p = Path.of(file); + return Files.newOutputStream(p); + } +} diff --git a/core/src/main/java/io/xpipe/core/store/MachineStore.java b/core/src/main/java/io/xpipe/core/store/MachineStore.java new file mode 100644 index 000000000..3dced52e8 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/MachineStore.java @@ -0,0 +1,16 @@ +package io.xpipe.core.store; + +import java.io.InputStream; +import java.io.OutputStream; + +public interface MachineStore extends DataStore { + + static MachineStore local() { + return new LocalMachineStore(); + } + + InputStream openInput(String file) throws Exception; + + OutputStream openOutput(String file) throws Exception; + +} diff --git a/core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java b/core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java deleted file mode 100644 index 7233db0d6..000000000 --- a/core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.xpipe.core.store; - -import java.io.InputStream; -import java.io.OutputStream; -import java.time.Instant; -import java.util.Optional; - -public class RemoteFileDataStore implements StreamDataStore { - - @Override - public Optional determineDefaultName() { - return Optional.empty(); - } - - @Override - public Optional determineLastModified() { - return Optional.empty(); - } - - @Override - public InputStream openInput() throws Exception { - return null; - } - - @Override - public OutputStream openOutput() throws Exception { - return null; - } - - @Override - public boolean exists() { - return false; - } -} diff --git a/core/src/main/java/io/xpipe/core/store/StreamDataStore.java b/core/src/main/java/io/xpipe/core/store/StreamDataStore.java index 6bcfe55f7..2041b048a 100644 --- a/core/src/main/java/io/xpipe/core/store/StreamDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StreamDataStore.java @@ -1,14 +1,7 @@ package io.xpipe.core.store; -import lombok.NonNull; - import java.io.InputStream; import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.util.Optional; /** * A data store that can be accessed using InputStreams and/or OutputStreams. @@ -16,21 +9,6 @@ import java.util.Optional; */ public interface StreamDataStore extends DataStore { - static Optional fromString(@NonNull String s) { - try { - var path = Path.of(s); - return Optional.of(new LocalFileDataStore(path)); - } catch (InvalidPathException ignored) { - } - - try { - var path = new URL(s); - } catch (MalformedURLException ignored) { - } - - return Optional.empty(); - } - /** * Opens an input stream. This input stream does not necessarily have to be a new instance. */ diff --git a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java index cd6e3b068..9e4905b30 100644 --- a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java +++ b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java @@ -11,19 +11,20 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.module.SimpleModule; -import io.xpipe.core.dialog.BaseQueryElement; -import io.xpipe.core.dialog.ChoiceElement; -import io.xpipe.core.dialog.HeaderElement; import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.ValueType; import io.xpipe.core.data.type.WildcardType; +import io.xpipe.core.dialog.BaseQueryElement; +import io.xpipe.core.dialog.BusyElement; +import io.xpipe.core.dialog.ChoiceElement; +import io.xpipe.core.dialog.HeaderElement; import io.xpipe.core.source.DataSourceInfo; import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.store.CollectionEntryDataStore; -import io.xpipe.core.store.HttpRequestStore; +import io.xpipe.core.store.FileStore; import io.xpipe.core.store.LocalDirectoryDataStore; -import io.xpipe.core.store.LocalFileDataStore; +import io.xpipe.core.store.LocalMachineStore; import java.io.IOException; import java.nio.charset.Charset; @@ -34,10 +35,9 @@ public class CoreJacksonModule extends SimpleModule { @Override public void setupModule(SetupContext context) { context.registerSubtypes( - new NamedType(LocalFileDataStore.class), + new NamedType(FileStore.class), new NamedType(LocalDirectoryDataStore.class), new NamedType(CollectionEntryDataStore.class), - new NamedType(HttpRequestStore.class), new NamedType(ValueType.class), new NamedType(TupleType.class), new NamedType(ArrayType.class), @@ -49,6 +49,8 @@ public class CoreJacksonModule extends SimpleModule { new NamedType(DataSourceInfo.Raw.class), new NamedType(BaseQueryElement.class), new NamedType(ChoiceElement.class), + new NamedType(BusyElement.class), + new NamedType(LocalMachineStore.class), new NamedType(HeaderElement.class) ); @@ -58,6 +60,9 @@ public class CoreJacksonModule extends SimpleModule { addSerializer(Path.class, new LocalPathSerializer()); addDeserializer(Path.class, new LocalPathDeserializer()); + addSerializer(Secret.class, new SecretSerializer()); + addDeserializer(Secret.class, new SecretDeserializer()); + addSerializer(DataSourceReference.class, new DataSourceReferenceSerializer()); addDeserializer(DataSourceReference.class, new DataSourceReferenceDeserializer()); @@ -119,6 +124,23 @@ public class CoreJacksonModule extends SimpleModule { } } + public static class SecretSerializer extends JsonSerializer { + + @Override + public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + jgen.writeString(value.getValue()); + } + } + + public static class SecretDeserializer extends JsonDeserializer { + + @Override + public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return Secret.parse(p.getValueAsString()); + } + } + @JsonSerialize(as = Throwable.class) public abstract static class ThrowableTypeMixIn { diff --git a/core/src/main/java/io/xpipe/core/util/Secret.java b/core/src/main/java/io/xpipe/core/util/Secret.java new file mode 100644 index 000000000..29d7592d7 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/Secret.java @@ -0,0 +1,30 @@ +package io.xpipe.core.util; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +@AllArgsConstructor +@EqualsAndHashCode +public class Secret { + + public static Secret parse(String s) { + return new Secret(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8))); + } + + String value; + + public String getDisplay() { + return "*".repeat(value.length()); + } + + public String getValue() { + return value; + } + + public String getSecret() { + return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index 3f8bc6ff0..2aba60d9a 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -1,8 +1,8 @@ package io.xpipe.extension; import io.xpipe.charsetter.NewLine; +import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.QueryConverter; -import io.xpipe.core.dialog.ConfigParameter; import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSourceType; import io.xpipe.core.store.DataStore; @@ -10,10 +10,10 @@ import javafx.beans.property.Property; import javafx.scene.layout.Region; import lombok.SneakyThrows; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.function.Function; public interface DataSourceProvider> { @@ -90,57 +90,27 @@ public interface DataSourceProvider> { } - interface ConfigProvider> { + public static Dialog charset(Charset c) { + return Dialog.query("charset", false, false, c, QueryConverter.CHARSET); + } - static > ConfigProvider empty(List names, Function func) { - return new ConfigProvider<>() { - @Override - public void applyConfig(T source, Map values) { - } + public static Dialog newLine(NewLine l) { + return Dialog.query("newline", false, false, l, NEW_LINE_CONVERTER); + } - @Override - public Map> toCompleteConfig() { - return Map.of(); - } - - @Override - public Map toRequiredReadConfig(T desc) { - return Map.of(); - } - - @Override - public List getPossibleNames() { - return names; - } - }; + public static final QueryConverter NEW_LINE_CONVERTER = new QueryConverter() { + @Override + protected NewLine fromString(String s) { + return NewLine.id(s); } - ConfigParameter CHARSET = new ConfigParameter( - "charset", QueryConverter.CHARSET); + @Override + protected String toString(NewLine value) { + return value.getId(); + } + }; - public static final QueryConverter NEW_LINE_CONVERTER = new QueryConverter() { - @Override - protected NewLine fromString(String s) { - return NewLine.id(s); - } - - @Override - protected String toString(NewLine value) { - return value.getId(); - } - }; - - ConfigParameter NEWLINE = new ConfigParameter( - "newline", NEW_LINE_CONVERTER); - - void applyConfig(T source, Map values); - - Map> toCompleteConfig(); - - Map toRequiredReadConfig(T desc); - - List getPossibleNames(); - } + Dialog configDialog(T source, boolean all); default boolean isHidden() { return false; @@ -184,12 +154,8 @@ public interface DataSourceProvider> { return false; } - ConfigProvider getConfigProvider(); - default String getId() { - var n = getClass().getPackageName(); - var i = n.lastIndexOf('.'); - return i != -1 ? n.substring(i + 1) : n; + return getPossibleNames().get(0); } /** @@ -208,4 +174,7 @@ public interface DataSourceProvider> { .filter(c -> c.getName().endsWith("Source")).findFirst() .orElseThrow(() -> new ExtensionException("Descriptor class not found for " + getId())); } + + + List getPossibleNames(); } diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java index 7357691a7..0d6b94f29 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java @@ -2,7 +2,7 @@ package io.xpipe.extension; import io.xpipe.core.source.*; import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.LocalFileDataStore; +import io.xpipe.core.store.FileStore; import io.xpipe.extension.event.ErrorEvent; import lombok.SneakyThrows; @@ -46,40 +46,40 @@ public class DataSourceProviders { @SuppressWarnings("unchecked") @SneakyThrows - public static StructureDataSource createLocalStructureDescriptor(DataStore store) { - return (StructureDataSource) + public static StructureDataSource createLocalStructureDescriptor(DataStore store) { + return (StructureDataSource) DataSourceProviders.byId("xpbs").getSourceClass() .getDeclaredConstructors()[0].newInstance(store); } @SuppressWarnings("unchecked") @SneakyThrows - public static RawDataSource createLocalRawDescriptor(DataStore store) { - return (RawDataSource) + public static RawDataSource createLocalRawDescriptor(DataStore store) { + return (RawDataSource) DataSourceProviders.byId("binary").getSourceClass() .getDeclaredConstructors()[0].newInstance(store); } @SuppressWarnings("unchecked") @SneakyThrows - public static RawDataSource createLocalCollectionDescriptor(DataStore store) { - return (RawDataSource) + public static RawDataSource createLocalCollectionDescriptor(DataStore store) { + return (RawDataSource) DataSourceProviders.byId("br").getSourceClass() .getDeclaredConstructors()[0].newInstance(store); } @SuppressWarnings("unchecked") @SneakyThrows - public static TextDataSource createLocalTextDescriptor(DataStore store) { - return (TextDataSource) + public static TextDataSource createLocalTextDescriptor(DataStore store) { + return (TextDataSource) DataSourceProviders.byId("text").getSourceClass() .getDeclaredConstructors()[0].newInstance(store); } @SuppressWarnings("unchecked") @SneakyThrows - public static TableDataSource createLocalTableDescriptor(DataStore store) { - return (TableDataSource) + public static TableDataSource createLocalTableDescriptor(DataStore store) { + return (TableDataSource) DataSourceProviders.byId("xpbt").getSourceClass() .getDeclaredConstructors()[0].newInstance(store); } @@ -110,7 +110,7 @@ public class DataSourceProviders { throw new IllegalStateException("Not initialized"); } - return ALL.stream().filter(d -> d.getConfigProvider().getPossibleNames().stream() + return ALL.stream().filter(d -> d.getPossibleNames().stream() .anyMatch(s -> s.equalsIgnoreCase(name))).findAny(); } diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java index f5097e87e..391480642 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java @@ -1,6 +1,7 @@ package io.xpipe.extension; import io.xpipe.core.dialog.Dialog; +import io.xpipe.core.store.DataStore; import java.net.URL; import java.util.List; @@ -34,13 +35,19 @@ public interface DataStoreProvider { return null; } + default Dialog dialogForString(String s) { + return null; + } + Dialog defaultDialog(); + default String display(DataStore store) { + return store.toDisplay(); + } + List getPossibleNames(); default String getId() { - var n = getClass().getPackageName(); - var i = n.lastIndexOf('.'); - return i != -1 ? n.substring(i + 1) : n; + return getPossibleNames().get(0); } } diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java b/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java index b2d54521d..ab1050d52 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java @@ -45,6 +45,14 @@ public class DataStoreProviders { return ALL.stream().map(d -> d.dialogForURL(url)).filter(Objects::nonNull).findAny(); } + public static Optional byString(String s) { + if (ALL == null) { + throw new IllegalStateException("Not initialized"); + } + + return ALL.stream().map(d -> d.dialogForString(s)).filter(Objects::nonNull).findAny(); + } + public static Set getAll() { return ALL; } diff --git a/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java index 5b61d0bae..61a550527 100644 --- a/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java @@ -2,7 +2,7 @@ package io.xpipe.extension; import io.xpipe.core.source.DataSource; import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.FileDataStore; +import io.xpipe.core.store.FileStore; import io.xpipe.core.store.StreamDataStore; import java.util.LinkedHashMap; @@ -23,7 +23,7 @@ public interface SimpleFileDataSourceProvider> extends D continue; } - if (store instanceof FileDataStore l) { + if (store instanceof FileStore l) { return l.getFileName().endsWith("." + ext); } } diff --git a/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java index 85806bc36..5b9ee6237 100644 --- a/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java @@ -1,16 +1,16 @@ package io.xpipe.extension; +import io.xpipe.core.dialog.Dialog; import io.xpipe.core.source.DataSource; import io.xpipe.core.store.DataStore; import java.lang.reflect.InvocationTargetException; -import java.util.List; public interface UniformDataSourceProvider> extends DataSourceProvider { @Override - default ConfigProvider getConfigProvider() { - return ConfigProvider.empty(List.of(getId()), this::createDefaultSource); + default Dialog configDialog(T source, boolean all) { + return Dialog.empty(); } @Override diff --git a/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java b/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java index 5fcf6b6fb..093617051 100644 --- a/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java +++ b/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java @@ -10,5 +10,9 @@ public interface PrefsChoiceValue extends Translatable { return I18n.get(getId()); } + default String toDefaultString() { + return I18n.get(getId()); + } + String getId(); }