mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-08-23 09:46:36 +00:00
Rework exchanges, rework stores, add dialog API
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
package io.xpipe.core.connection;
|
||||
|
||||
public class Connection {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Element> elements;
|
||||
private final String description;
|
||||
private final List<Choice> 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<Element> elements, int selected) {
|
||||
public ChoiceElement(String description, List<Choice> elements, int selected) {
|
||||
this.description = description;
|
||||
this.elements = elements;
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
public List<Choice> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public int getSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<io.xpipe.core.dialog.Choice> 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<io.xpipe.core.dialog.Choice> elements, int selected) {
|
||||
Dialog.Choice c = new Dialog.Choice(description, elements, selected);
|
||||
c.evaluateTo(c::getSelected);
|
||||
return c;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Dialog.Choice choice(String description, Function<T, String> 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<Map.Entry<String, String>> 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> 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<Boolean> shouldRepeat) {
|
||||
public static <T> Dialog repeatIf(Dialog d, Predicate<T> 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<String> 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> {
|
||||
|
||||
T get() throws Exception;
|
||||
}
|
||||
|
||||
public static Dialog lazy(FailableSupplier<Dialog> 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<String> msg) {
|
||||
|
||||
public static Dialog skipIf(Dialog d, Supplier<Boolean> 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 <T> Dialog retryIf(Dialog d, Function<T, String> 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<Integer, Dialog> c) {
|
||||
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, int selected, Function<Integer, Dialog> 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<Consumer<?>> completion = new ArrayList<>();
|
||||
|
||||
public abstract DialogElement start();
|
||||
public abstract DialogElement start() throws Exception;
|
||||
|
||||
public Optional<Map.Entry<String, String>> 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 <T> Dialog map(Function<T, ?> 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<Consumer<?>> 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 <T> void complete() {
|
||||
if (evaluation != null) {
|
||||
eval = evaluation.get();
|
||||
completion.forEach(c -> {
|
||||
Consumer<T> ct = (Consumer<T>) 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<T> {
|
||||
|
||||
@@ -30,18 +34,51 @@ public abstract class QueryConverter<T> {
|
||||
}
|
||||
};
|
||||
|
||||
public static final QueryConverter<URL> URL = new QueryConverter<URL>() {
|
||||
public static final QueryConverter<Secret> SECRET = new QueryConverter<Secret>() {
|
||||
@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<Map.Entry<String, String>> HTTP_HEADER = new QueryConverter<Map.Entry<String, String>>() {
|
||||
@Override
|
||||
protected Map.Entry<String, String> 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<String, String> value) {
|
||||
return value.getKey() + ": " + value.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
public static final QueryConverter<URI> URI = new QueryConverter<URI>() {
|
||||
@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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-6
@@ -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<String> 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();
|
||||
}
|
||||
@@ -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<HttpRequestStore> 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<String, String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Instant> 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<String> determineDefaultName() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Instant> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<StreamDataStore> 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.
|
||||
*/
|
||||
|
||||
@@ -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<Secret> {
|
||||
|
||||
@Override
|
||||
public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
jgen.writeString(value.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecretDeserializer extends JsonDeserializer<Secret> {
|
||||
|
||||
@Override
|
||||
public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Secret.parse(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(as = Throwable.class)
|
||||
public abstract static class ThrowableTypeMixIn {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user