mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Add more exchanges + move some files
This commit is contained in:
parent
774689e42a
commit
caafb9d850
28 changed files with 775 additions and 76 deletions
|
@ -23,7 +23,7 @@ import java.util.Optional;
|
|||
|
||||
import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
||||
|
||||
public class BeaconClient {
|
||||
public class BeaconClient implements AutoCloseable {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FailableBiConsumer<T, U, E extends Throwable> {
|
||||
|
@ -76,7 +76,7 @@ public class BeaconClient {
|
|||
public <REQ extends RequestMessage, RES extends ResponseMessage> void exchange(
|
||||
REQ req,
|
||||
FailableConsumer<OutputStream, IOException> reqWriter,
|
||||
FailableBiPredicate<RES, InputStream, IOException> resReader)
|
||||
FailableBiConsumer<RES, InputStream, IOException> resReader)
|
||||
throws ConnectorException, ClientException, ServerException {
|
||||
try {
|
||||
sendRequest(req);
|
||||
|
@ -91,23 +91,16 @@ public class BeaconClient {
|
|||
throw new ConnectorException("Invalid body separator");
|
||||
}
|
||||
|
||||
if (resReader.test(res, in)) {
|
||||
close();
|
||||
}
|
||||
resReader.accept(res, in);
|
||||
} catch (IOException ex) {
|
||||
close();
|
||||
throw new ConnectorException("Couldn't communicate with socket", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public <REQ extends RequestMessage, RES extends ResponseMessage> RES simpleExchange(REQ req)
|
||||
throws ServerException, ConnectorException, ClientException {
|
||||
try {
|
||||
sendRequest(req);
|
||||
return this.receiveResponse();
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
throws ServerException, ConnectorException, ClientException {
|
||||
sendRequest(req);
|
||||
return this.receiveResponse();
|
||||
}
|
||||
|
||||
private <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
|
||||
|
|
|
@ -16,7 +16,7 @@ public abstract class BeaconConnector {
|
|||
protected <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
|
||||
BeaconClient socket,
|
||||
REQ req,
|
||||
BeaconClient.FailableBiPredicate<RES, InputStream, IOException> responseConsumer) throws ServerException, ConnectorException, ClientException {
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer) throws ServerException, ConnectorException, ClientException {
|
||||
performInputOutputExchange(socket, req, null, responseConsumer);
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ public abstract class BeaconConnector {
|
|||
BeaconClient socket,
|
||||
REQ req,
|
||||
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
|
||||
BeaconClient.FailableBiPredicate<RES, InputStream, IOException> responseConsumer)
|
||||
BeaconClient.FailableBiConsumer<RES, InputStream, IOException> responseConsumer)
|
||||
throws ServerException, ConnectorException, ClientException {
|
||||
socket.exchange(req, reqWriter, responseConsumer);
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ public abstract class BeaconConnector {
|
|||
AtomicReference<RES> response = new AtomicReference<>();
|
||||
socket.exchange(req, reqWriter, (RES res, InputStream in) -> {
|
||||
response.set(res);
|
||||
return true;
|
||||
});
|
||||
return response.get();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package io.xpipe.beacon.exchange;
|
||||
|
||||
import io.xpipe.beacon.message.RequestMessage;
|
||||
import io.xpipe.beacon.message.ResponseMessage;
|
||||
import io.xpipe.core.source.DataSourceConfigInstance;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class DialogExchange implements MessageExchange<DialogExchange.Request, DialogExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "dialog";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<DialogExchange.Request> getRequestClass() {
|
||||
return DialogExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<DialogExchange.Response> getResponseClass() {
|
||||
return DialogExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
DataSourceConfigInstance instance;
|
||||
String key;
|
||||
String value;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
DataSourceConfigInstance instance;
|
||||
String errorMsg;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
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.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class InfoExchange implements MessageExchange<InfoExchange.Request, InfoExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "info";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<InfoExchange.Request> getRequestClass() {
|
||||
return InfoExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<InfoExchange.Response> getResponseClass() {
|
||||
return InfoExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
DataSourceId id;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
DataSourceInfo info;
|
||||
DataStore store;
|
||||
DataSourceConfigInstance config;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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.DataSourceId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class ReadExecuteExchange implements MessageExchange<ReadExecuteExchange.Request, ReadExecuteExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "readExecute";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ReadExecuteExchange.Request> getRequestClass() {
|
||||
return ReadExecuteExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ReadExecuteExchange.Response> getResponseClass() {
|
||||
return ReadExecuteExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
@NonNull
|
||||
DataStore dataStore;
|
||||
@NonNull
|
||||
DataSourceConfigInstance config;
|
||||
@NonNull
|
||||
DataSourceId targetId;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
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.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class ReadPreparationExchange implements MessageExchange<ReadPreparationExchange.Request, ReadPreparationExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "readPreparation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ReadPreparationExchange.Request> getRequestClass() {
|
||||
return ReadPreparationExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ReadPreparationExchange.Response> getResponseClass() {
|
||||
return ReadPreparationExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
String providerType;
|
||||
String dataStore;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
DataSourceConfigInstance config;
|
||||
DataStore dataStore;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.xpipe.beacon.exchange;
|
||||
|
||||
import io.xpipe.beacon.message.RequestMessage;
|
||||
import io.xpipe.beacon.message.ResponseMessage;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class SelectExchange implements MessageExchange<SelectExchange.Request, SelectExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "select";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<SelectExchange.Request> getRequestClass() {
|
||||
return SelectExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<SelectExchange.Response> getResponseClass() {
|
||||
return SelectExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
DataSourceId id;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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.DataSourceId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class WriteExecuteExchange implements MessageExchange<WriteExecuteExchange.Request, WriteExecuteExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "writeExecute";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WriteExecuteExchange.Request> getRequestClass() {
|
||||
return WriteExecuteExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WriteExecuteExchange.Response> getResponseClass() {
|
||||
return WriteExecuteExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
@NonNull
|
||||
DataSourceId sourceId;
|
||||
@NonNull
|
||||
DataStore dataStore;
|
||||
@NonNull
|
||||
DataSourceConfigInstance config;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
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.DataSourceId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class WritePreparationExchange implements MessageExchange<WritePreparationExchange.Request, WritePreparationExchange.Response> {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "writePreparation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WritePreparationExchange.Request> getRequestClass() {
|
||||
return WritePreparationExchange.Request.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WritePreparationExchange.Response> getResponseClass() {
|
||||
return WritePreparationExchange.Response.class;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
String providerType;
|
||||
String output;
|
||||
@NonNull
|
||||
DataSourceId sourceId;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
@NonNull
|
||||
DataStore dataStore;
|
||||
|
||||
@NonNull
|
||||
DataSourceConfigInstance config;
|
||||
}
|
||||
}
|
|
@ -25,5 +25,12 @@ module io.xpipe.beacon {
|
|||
StatusExchange,
|
||||
StopExchange,
|
||||
StoreResourceExchange,
|
||||
WritePreparationExchange,
|
||||
WriteExecuteExchange,
|
||||
SelectExchange,
|
||||
ReadPreparationExchange,
|
||||
ReadExecuteExchange,
|
||||
DialogExchange,
|
||||
InfoExchange,
|
||||
VersionExchange;
|
||||
}
|
|
@ -50,6 +50,10 @@ public class TupleType extends DataType {
|
|||
return new TupleType(Collections.nCopies(types.size(), null), types);
|
||||
}
|
||||
|
||||
public boolean hasAllNames() {
|
||||
return names.stream().allMatch(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "tuple";
|
||||
|
|
|
@ -1,49 +1,29 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Singular;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
public class DataSourceConfig {
|
||||
String description;
|
||||
|
||||
private String description;
|
||||
private List<Option<?>> options;
|
||||
@Singular
|
||||
List<Option> options;
|
||||
|
||||
public DataSourceConfig(String description, List<Option<?>> options) {
|
||||
this.description = description;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public List<Option<?>> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public abstract static class Option<T> {
|
||||
|
||||
private final String name;
|
||||
protected T value;
|
||||
|
||||
public Option(String name) {
|
||||
this.name = name;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
public Option(String name, T value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
protected abstract String enterValue(String val);
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@AllArgsConstructor
|
||||
public static class Option {
|
||||
String name;
|
||||
String key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@AllArgsConstructor
|
||||
public class DataSourceConfigInstance {
|
||||
|
||||
String provider;
|
||||
DataSourceConfig config;
|
||||
Map<String, String> currentValues;
|
||||
}
|
|
@ -22,8 +22,11 @@ import lombok.Getter;
|
|||
public class DataSourceId {
|
||||
|
||||
public static final char SEPARATOR = ':';
|
||||
public static final DataSourceId ANONYMOUS = new DataSourceId(null, null);
|
||||
|
||||
private final String collectionName;
|
||||
private final String entryName;
|
||||
|
||||
@JsonCreator
|
||||
private DataSourceId(String collectionName, String entryName) {
|
||||
this.collectionName = collectionName;
|
||||
|
@ -38,6 +41,10 @@ public class DataSourceId {
|
|||
* @throws IllegalArgumentException if any name is not valid
|
||||
*/
|
||||
public static DataSourceId create(String collectionName, String entryName) {
|
||||
if (collectionName == null && entryName == null) {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
if (collectionName != null && collectionName.trim().length() == 0) {
|
||||
throw new IllegalArgumentException("Trimmed collection name is empty");
|
||||
}
|
||||
|
@ -70,6 +77,10 @@ public class DataSourceId {
|
|||
throw new IllegalArgumentException("String is null");
|
||||
}
|
||||
|
||||
if (s.equals(String.valueOf(SEPARATOR))) {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
var split = s.split(String.valueOf(SEPARATOR));
|
||||
if (split.length != 2) {
|
||||
throw new IllegalArgumentException("Data source id must contain exactly one " + SEPARATOR);
|
||||
|
@ -87,6 +98,6 @@ public class DataSourceId {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (collectionName != null ? collectionName : "") + SEPARATOR + entryName;
|
||||
return (collectionName != null ? collectionName.toLowerCase() : "") + SEPARATOR + (entryName != null ? entryName.toLowerCase() : "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,4 +36,9 @@ public class InputStreamDataStore implements StreamDataStore {
|
|||
public OutputStream openOutput() throws Exception {
|
||||
throw new UnsupportedOperationException("No output available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
@ -13,6 +14,7 @@ import java.time.Instant;
|
|||
import java.util.Optional;
|
||||
|
||||
@JsonTypeName("local")
|
||||
@EqualsAndHashCode
|
||||
public class LocalFileDataStore implements StreamDataStore {
|
||||
|
||||
private final Path file;
|
||||
|
@ -50,4 +52,9 @@ public class LocalFileDataStore implements StreamDataStore {
|
|||
public OutputStream openOutput() throws Exception {
|
||||
return Files.newOutputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(file);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,9 @@ public class RemoteFileDataStore implements StreamDataStore {
|
|||
public OutputStream openOutput() throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
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.
|
||||
|
@ -9,6 +16,21 @@ import java.io.OutputStream;
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -18,4 +40,6 @@ public interface StreamDataStore extends DataStore {
|
|||
* Opens an output stream. This output stream does not necessarily have to be a new instance.
|
||||
*/
|
||||
OutputStream openOutput() throws Exception;
|
||||
|
||||
boolean exists();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.DeserializationContext;
|
|||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
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.data.type.ArrayType;
|
||||
|
@ -35,6 +36,8 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
|
||||
addSerializer(Path.class, new LocalPathSerializer());
|
||||
addDeserializer(Path.class, new LocalPathDeserializer());
|
||||
|
||||
context.setMixInAnnotations(Throwable.class, ExceptionTypeMixIn.class);
|
||||
}
|
||||
|
||||
public static class CharsetSerializer extends JsonSerializer<Charset> {
|
||||
|
@ -70,4 +73,10 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
return Path.of(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(as = Throwable.class)
|
||||
public abstract static class ExceptionTypeMixIn {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ plugins {
|
|||
|
||||
apply from: "$rootDir/deps/java.gradle"
|
||||
apply from: "$rootDir/deps/javafx.gradle"
|
||||
apply from: "$rootDir/deps/richtextfx.gradle"
|
||||
apply from: "$rootDir/deps/jackson.gradle"
|
||||
apply from: "$rootDir/deps/commons.gradle"
|
||||
apply from: "$rootDir/deps/lombok.gradle"
|
||||
apply from: "$rootDir/deps/ikonli.gradle"
|
||||
apply from: 'publish.gradle'
|
||||
apply from: "$rootDir/deps/publish-base.gradle"
|
||||
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.source.DataSourceConfig;
|
||||
import io.xpipe.core.source.DataSourceDescriptor;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.source.TableReadConnection;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface DataSourceProvider {
|
||||
|
||||
interface FileProvider {
|
||||
|
||||
boolean supportsFile(Path file);
|
||||
void write(StreamDataStore store, DataSourceDescriptor<StreamDataStore> desc, TableReadConnection con) throws Exception;
|
||||
|
||||
String getFileName();
|
||||
|
||||
|
@ -33,8 +37,45 @@ public interface DataSourceProvider {
|
|||
|
||||
interface CliProvider {
|
||||
|
||||
static String booleanName(String name) {
|
||||
return name + " (y/n)";
|
||||
}
|
||||
|
||||
static Function<String, Boolean> booleanConverter() {
|
||||
return s -> {
|
||||
if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (s.equalsIgnoreCase("n") || s.equalsIgnoreCase("no")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid boolean: " + s);
|
||||
};
|
||||
}
|
||||
|
||||
static Function<String, Character> characterConverter() {
|
||||
return s -> {
|
||||
if (s.length() != 1) {
|
||||
throw new IllegalArgumentException("Invalid character: " + s);
|
||||
}
|
||||
|
||||
return s.toCharArray()[0];
|
||||
};
|
||||
}
|
||||
|
||||
DataSourceConfig getConfig();
|
||||
|
||||
DataSourceDescriptor<?> toDescriptor(Map<String, String> values);
|
||||
|
||||
Map<String, String> toConfigOptions(DataSourceDescriptor<?> desc);
|
||||
|
||||
Map<DataSourceConfig.Option, Function<String, ?>> getConverters();
|
||||
}
|
||||
|
||||
boolean supportsStore(DataStore store);
|
||||
|
||||
FileProvider getFileProvider();
|
||||
|
||||
GuiProvider getGuiProvider();
|
||||
|
@ -49,5 +90,7 @@ public interface DataSourceProvider {
|
|||
*/
|
||||
DataSourceDescriptor<?> createDefaultDescriptor(DataStore input) throws Exception;
|
||||
|
||||
DataSourceDescriptor<?> createDefaultWriteDescriptor(DataStore input, DataSourceInfo info) throws Exception;
|
||||
|
||||
Class<? extends DataSourceDescriptor<?>> getDescriptorClass();
|
||||
}
|
||||
|
|
|
@ -2,12 +2,10 @@ package io.xpipe.extension;
|
|||
|
||||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.source.TableDataSourceDescriptor;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.LocalFileDataStore;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
@ -52,26 +50,13 @@ public class DataSourceProviders {
|
|||
return ALL.stream().filter(d -> d.getId().equals(name)).findAny();
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider> byFile(Path file) {
|
||||
public static Optional<DataSourceProvider> byStore(DataStore store) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream().filter(d -> d.getFileProvider() != null)
|
||||
.filter(d -> d.getFileProvider().supportsFile(file)).findAny();
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider> byURL(URL url) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
var path = Path.of(url.toURI());
|
||||
return byFile(path);
|
||||
} catch (URISyntaxException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
.filter(d -> d.supportsStore(store)).findAny();
|
||||
}
|
||||
|
||||
public static Set<DataSourceProvider> getAll() {
|
||||
|
|
|
@ -8,6 +8,11 @@ import java.util.function.Supplier;
|
|||
|
||||
public interface SupportedApplicationProvider {
|
||||
|
||||
enum Category {
|
||||
PROGRAMMING_LANGUAGE,
|
||||
APPLICATION
|
||||
}
|
||||
|
||||
Region createTableRetrieveInstructions(ObservableValue<DataSourceId> id);
|
||||
|
||||
Region createStructureRetrieveInstructions(ObservableValue<DataSourceId> id);
|
||||
|
@ -19,4 +24,12 @@ public interface SupportedApplicationProvider {
|
|||
String getId();
|
||||
|
||||
Supplier<String> getName();
|
||||
|
||||
Category getCategory();
|
||||
|
||||
String getSetupGuideURL();
|
||||
|
||||
default String getGraphicIcon() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||
|
||||
public String getRawString() {
|
||||
return lines.stream().map(line -> line.elements().stream()
|
||||
.map(Element::text).collect(Collectors.joining()))
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
|
||||
public static Builder builder(CodeSnippets.ColorScheme scheme) {
|
||||
return new Builder(scheme);
|
||||
}
|
||||
|
||||
public static interface Element {
|
||||
|
||||
String text();
|
||||
|
||||
Color color();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private CodeSnippets.ColorScheme scheme;
|
||||
private List<Line> lines;
|
||||
private List<Element> currentLine;
|
||||
|
||||
public Builder(CodeSnippets.ColorScheme scheme) {
|
||||
this.scheme = scheme;
|
||||
lines = new ArrayList<>();
|
||||
currentLine = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Builder keyword(String text) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(text, scheme.keyword()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder string(String text) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(text, scheme.string()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder identifier(String text) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(text, scheme.identifier()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder type(String text) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(text, scheme.type()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder text(String text, Color c) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(text, c));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder space() {
|
||||
currentLine.add(new CodeSnippet.StaticElement(" ", Color.TRANSPARENT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder space(int count) {
|
||||
currentLine.add(new CodeSnippet.StaticElement(" ".repeat(count), Color.TRANSPARENT));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder newLine() {
|
||||
lines.add(new Line(new ArrayList<>(currentLine)));
|
||||
currentLine.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeSnippet build() {
|
||||
if (currentLine.size() > 0) {
|
||||
newLine();
|
||||
}
|
||||
return new CodeSnippet(lines);
|
||||
}
|
||||
}
|
||||
|
||||
public static record StaticElement(String value, Color color) implements Element {
|
||||
|
||||
@Override
|
||||
public String text() {
|
||||
return value();
|
||||
}
|
||||
}
|
||||
|
||||
public static record Line(List<CodeSnippet.Element> elements) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformUtil;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.fxmisc.richtext.InlineCssTextArea;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
|
||||
|
||||
public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
||||
|
||||
private final ObservableValue<CodeSnippet> value;
|
||||
|
||||
public CodeSnippetComp(ObservableValue<CodeSnippet> value) {
|
||||
this.value = PlatformUtil.wrap(value);
|
||||
}
|
||||
|
||||
private static String toRGBCode(Color color) {
|
||||
return String.format("#%02X%02X%02X",
|
||||
(int) (color.getRed() * 255),
|
||||
(int) (color.getGreen() * 255),
|
||||
(int) (color.getBlue() * 255));
|
||||
}
|
||||
|
||||
private void fillArea(VBox lineNumbers, InlineCssTextArea s) {
|
||||
lineNumbers.getChildren().clear();
|
||||
s.clear();
|
||||
|
||||
int number = 1;
|
||||
for (CodeSnippet.Line line : value.getValue().lines()) {
|
||||
var numberLabel = new Label(String.valueOf(number));
|
||||
numberLabel.getStyleClass().add("line-number");
|
||||
lineNumbers.getChildren().add(numberLabel);
|
||||
|
||||
for (var el : line.elements()) {
|
||||
String hex = toRGBCode(el.color());
|
||||
s.append(el.text(), "-fx-fill: " + hex + ";");
|
||||
}
|
||||
|
||||
boolean last = number == value.getValue().lines().size();
|
||||
if (!last) {
|
||||
s.appendText("\n");
|
||||
}
|
||||
|
||||
number++;
|
||||
}
|
||||
}
|
||||
|
||||
private Region createCopyButton(Region container) {
|
||||
var button = new Button();
|
||||
button.setGraphic(new FontIcon("mdoal-content_copy"));
|
||||
button.setOnAction(e -> {
|
||||
var string = new StringSelection(value.getValue().getRawString());
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(string, string);
|
||||
});
|
||||
button.getStyleClass().add("copy");
|
||||
button.getStyleClass().add("button-comp");
|
||||
button.visibleProperty().bind(container.hoverProperty());
|
||||
return button;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<StackPane> createBase() {
|
||||
var s = new InlineCssTextArea();
|
||||
s.setEditable(false);
|
||||
s.setBackground(null);
|
||||
s.getStyleClass().add("code-snippet");
|
||||
s.addEventFilter(ScrollEvent.ANY, e -> {
|
||||
s.getParent().fireEvent(e);
|
||||
e.consume();
|
||||
});
|
||||
|
||||
var lineNumbers = new VBox();
|
||||
lineNumbers.getStyleClass().add("line-numbers");
|
||||
fillArea(lineNumbers, s);
|
||||
value.addListener((c,o,n) -> {
|
||||
PlatformUtil.runLaterIfNeeded(() -> {
|
||||
fillArea(lineNumbers, s);
|
||||
});
|
||||
});
|
||||
|
||||
var spacer = new Region();
|
||||
spacer.getStyleClass().add("spacer");
|
||||
|
||||
var content = new HBox(lineNumbers, spacer, s);
|
||||
HBox.setHgrow(s, Priority.ALWAYS);
|
||||
var container = new ScrollPane(content);
|
||||
container.setFitToWidth(true);
|
||||
|
||||
var c = new StackPane(container);
|
||||
c.getStyleClass().add("code-snippet-container");
|
||||
|
||||
var copyButton = createCopyButton(c);
|
||||
var pane = new AnchorPane(copyButton);
|
||||
AnchorPane.setTopAnchor(copyButton, 10.0);
|
||||
AnchorPane.setRightAnchor(copyButton, 10.0);
|
||||
c.getChildren().add(pane);
|
||||
|
||||
return new CompStructure<>(c);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class CodeSnippets {
|
||||
|
||||
public static final ColorScheme LIGHT_MODE = new ColorScheme(
|
||||
Color.valueOf("0033B3"),
|
||||
Color.valueOf("000000"),
|
||||
Color.valueOf("000000"),
|
||||
Color.valueOf("067D17")
|
||||
);
|
||||
|
||||
public static record ColorScheme(
|
||||
Color keyword, Color identifier, Color type, Color string) {
|
||||
|
||||
}
|
||||
}
|
|
@ -18,4 +18,12 @@ module io.xpipe.extension {
|
|||
uses SupportedApplicationProvider;
|
||||
uses io.xpipe.extension.I18n;
|
||||
uses io.xpipe.extension.event.EventHandler;
|
||||
|
||||
requires java.desktop;
|
||||
requires org.fxmisc.richtext;
|
||||
requires org.fxmisc.flowless;
|
||||
requires org.fxmisc.undofx;
|
||||
requires org.fxmisc.wellbehavedfx;
|
||||
requires org.reactfx;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
.code-snippet {
|
||||
-fx-padding: 0.3em 0.5em 0.3em 0.5em;
|
||||
-fx-border-width: 0 0 0 1px;
|
||||
-fx-border-color: -xp-border;
|
||||
-fx-spacing: 0;
|
||||
-fx-font-family: Monospace;
|
||||
}
|
||||
|
||||
.code-snippet-container {
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: -xp-border;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
|
||||
.copy {
|
||||
|
||||
}
|
||||
|
||||
.line-number {
|
||||
-fx-alignment: center-right;
|
||||
-fx-font-family: Monospace;
|
||||
-fx-text-fill: grey;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
-fx-padding: 0.3em 0.2em 0.3em 0.5em;
|
||||
-fx-background-color: #073B4C11;
|
||||
}
|
||||
|
||||
.code-snippet .line {
|
||||
-fx-padding: 0.1em 0 0 0;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.code-snippet .spacer {
|
||||
-fx-pref-width: 1px;
|
||||
-fx-background-color: #073B4C43;
|
||||
}
|
Loading…
Reference in a new issue