mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Rework and refactoring
This commit is contained in:
parent
6efad1fe42
commit
c7606f9f8e
82 changed files with 1618 additions and 396 deletions
|
@ -7,11 +7,10 @@ plugins {
|
|||
|
||||
apply from: "$rootDir/deps/java.gradle"
|
||||
apply from: "$rootDir/deps/junit.gradle"
|
||||
apply from: "$rootDir/deps/jackson.gradle"
|
||||
|
||||
version = file('../misc/version').text
|
||||
group = 'io.xpipe'
|
||||
archivesBaseName = 'api'
|
||||
archivesBaseName = 'xpipe-api'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -22,8 +21,9 @@ test {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
api project(':core')
|
||||
implementation project(':beacon')
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = project.archivesBaseName
|
||||
|
||||
from components.java
|
||||
|
||||
pom.withXml {
|
||||
def pomNode = asNode()
|
||||
pomNode.dependencies.'*'.findAll().each() {
|
||||
it.scope*.value = 'compile'
|
||||
}
|
||||
}
|
||||
|
||||
pom {
|
||||
name = 'X-Pipe Java API'
|
||||
description = 'Contains everything necessary to interact with X-Pipe from Java applications.'
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.api.impl.DataSourceImpl;
|
|||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -26,6 +27,10 @@ import java.nio.file.Path;
|
|||
*/
|
||||
public interface DataSource {
|
||||
|
||||
void forwardTo(DataSource target);
|
||||
|
||||
void appendTo(DataSource target);
|
||||
|
||||
/**
|
||||
* NOT YET IMPLEMENTED!
|
||||
*
|
||||
|
@ -141,6 +146,30 @@ public interface DataSource {
|
|||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data source from an input stream.
|
||||
*
|
||||
* @param id the data source id
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
public static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
return DataSourceImpl.create(id, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data source from an input stream.
|
||||
*1
|
||||
* @param id the data source id
|
||||
* @param type the data source type
|
||||
* @param in the data store to add
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
public static DataSource create(DataSourceId id, String type, DataStore in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
public io.xpipe.core.source.DataSource<?> getInternalSource();
|
||||
|
||||
/**
|
||||
* Returns the id of this data source.
|
||||
*/
|
||||
|
|
|
@ -26,7 +26,7 @@ public final class DataSourceConfig {
|
|||
return provider;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfigInstance() {
|
||||
public Map<String, String> getConfig() {
|
||||
return configInstance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ package io.xpipe.api;
|
|||
import io.xpipe.core.source.DataSourceInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface DataText extends DataSource, Iterable<String> {
|
||||
public interface DataText extends DataSource {
|
||||
|
||||
DataSourceInfo.Text getInfo();
|
||||
|
||||
|
@ -12,6 +13,8 @@ public interface DataText extends DataSource, Iterable<String> {
|
|||
|
||||
List<String> readLines(int maxLines);
|
||||
|
||||
Stream<String> lines();
|
||||
|
||||
String readAll();
|
||||
|
||||
String read(int maxCharacters);
|
||||
|
|
|
@ -18,8 +18,14 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
public static void finishDialog(DialogReference reference) {
|
||||
try (var con = new XPipeConnection()) {
|
||||
con.constructSocket();
|
||||
var element = reference.getStart();
|
||||
while (true) {
|
||||
if (element.requiresExplicitUserInput()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
DialogExchange.Response response = con.performSimpleExchange(DialogExchange.Request.builder().dialogKey(reference.getDialogId()).build());
|
||||
element = response.getElement();
|
||||
if (response.getElement() == null) {
|
||||
break;
|
||||
}
|
||||
|
@ -69,7 +75,7 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
throw new BeaconException("Unable to start xpipe daemon", ex);
|
||||
}
|
||||
|
||||
var r = waitForStartup();
|
||||
var r = waitForStartup(null);
|
||||
if (r.isEmpty()) {
|
||||
throw new BeaconException("Wait for xpipe daemon timed out");
|
||||
} else {
|
||||
|
@ -86,13 +92,17 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
}
|
||||
|
||||
private void start() throws Exception {
|
||||
if (!BeaconServer.tryStart()) {
|
||||
if (BeaconServer.tryStart() == null) {
|
||||
throw new UnsupportedOperationException("Unable to determine xpipe daemon launch command");
|
||||
};
|
||||
}
|
||||
|
||||
public static Optional<BeaconClient> waitForStartup() {
|
||||
for (int i = 0; i < 80; i++) {
|
||||
public static Optional<BeaconClient> waitForStartup(Process process) {
|
||||
for (int i = 0; i < 160; i++) {
|
||||
if (process != null && !process.isAlive()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ignored) {
|
||||
|
|
|
@ -10,8 +10,8 @@ public class DataRawImpl extends DataSourceImpl implements DataRaw {
|
|||
|
||||
private final DataSourceInfo.Raw info;
|
||||
|
||||
public DataRawImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Raw info) {
|
||||
super(sourceId, sourceConfig);
|
||||
public DataRawImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Raw info, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,40 @@ package io.xpipe.api.impl;
|
|||
import io.xpipe.api.DataSource;
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.beacon.exchange.QueryDataSourceExchange;
|
||||
import io.xpipe.beacon.exchange.ReadExchange;
|
||||
import io.xpipe.beacon.exchange.StoreStreamExchange;
|
||||
import io.xpipe.beacon.exchange.*;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class DataSourceImpl implements DataSource {
|
||||
|
||||
@Override
|
||||
public void forwardTo(DataSource target) {
|
||||
XPipeConnection.execute(con -> {
|
||||
var req = ForwardExchange.Request.builder()
|
||||
.source(DataSourceReference.id(sourceId))
|
||||
.target(DataSourceReference.id(target.getId()))
|
||||
.build();
|
||||
ForwardExchange.Response res = con.performSimpleExchange(req);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(DataSource target) {
|
||||
XPipeConnection.execute(con -> {
|
||||
var req = ForwardExchange.Request.builder()
|
||||
.source(DataSourceReference.id(sourceId))
|
||||
.target(DataSourceReference.id(target.getId()))
|
||||
.append(true)
|
||||
.build();
|
||||
ForwardExchange.Response res = con.performSimpleExchange(req);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static DataSource get(DataSourceReference ds) {
|
||||
return XPipeConnection.execute(con -> {
|
||||
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
||||
|
@ -21,19 +45,19 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
return switch (res.getInfo().getType()) {
|
||||
case TABLE -> {
|
||||
var data = res.getInfo().asTable();
|
||||
yield new DataTableImpl(res.getId(), config, data);
|
||||
yield new DataTableImpl(res.getId(), config, data, res.getInternalSource());
|
||||
}
|
||||
case STRUCTURE -> {
|
||||
var info = res.getInfo().asStructure();
|
||||
yield new DataStructureImpl(res.getId(), config, info);
|
||||
yield new DataStructureImpl(res.getId(), config, info, res.getInternalSource());
|
||||
}
|
||||
case TEXT -> {
|
||||
var info = res.getInfo().asText();
|
||||
yield new DataTextImpl(res.getId(), config, info);
|
||||
yield new DataTextImpl(res.getId(), config, info, res.getInternalSource());
|
||||
}
|
||||
case RAW -> {
|
||||
var info = res.getInfo().asRaw();
|
||||
yield new DataRawImpl(res.getId(), config, info);
|
||||
yield new DataRawImpl(res.getId(), config, info, res.getInternalSource());
|
||||
}
|
||||
case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getInfo().getType());
|
||||
default -> throw new IllegalArgumentException("Unexpected value: " + res.getInfo().getType());
|
||||
|
@ -41,6 +65,55 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
});
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
var startReq = AddSourceExchange.Request.builder()
|
||||
.source(source)
|
||||
.target(id)
|
||||
.build();
|
||||
var returnedId = XPipeConnection.execute(con -> {
|
||||
AddSourceExchange.Response r = con.performSimpleExchange(startReq);
|
||||
return r.getId();
|
||||
});
|
||||
|
||||
var ref = DataSourceReference.id(returnedId);
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
||||
if (store instanceof StreamDataStore s && s.isLocalToApplication()) {
|
||||
var res = XPipeConnection.execute(con -> {
|
||||
var req = StoreStreamExchange.Request.builder().build();
|
||||
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> {
|
||||
try (InputStream inputStream = s.openInput()) {
|
||||
inputStream.transferTo(out);
|
||||
}
|
||||
});
|
||||
return r;
|
||||
});
|
||||
|
||||
store = res.getStore();
|
||||
}
|
||||
|
||||
var startReq = ReadExchange.Request.builder()
|
||||
.provider(type)
|
||||
.store(store)
|
||||
.target(id)
|
||||
.configureAll(false)
|
||||
.build();
|
||||
var startRes = XPipeConnection.execute(con -> {
|
||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||
return r;
|
||||
});
|
||||
|
||||
var configInstance = startRes.getConfig();
|
||||
XPipeConnection.finishDialog(configInstance);
|
||||
|
||||
var ref = id != null ?
|
||||
DataSourceReference.id(id) :
|
||||
DataSourceReference.latest();
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||
var res = XPipeConnection.execute(con -> {
|
||||
var req = StoreStreamExchange.Request.builder().build();
|
||||
|
@ -64,16 +137,24 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
var configInstance = startRes.getConfig();
|
||||
XPipeConnection.finishDialog(configInstance);
|
||||
|
||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||
var ref = id != null ?
|
||||
DataSourceReference.id(id) :
|
||||
DataSourceReference.latest();
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
private final DataSourceId sourceId;
|
||||
private final DataSourceConfig config;
|
||||
private final io.xpipe.core.source.DataSource<?> internalSource;
|
||||
|
||||
public DataSourceImpl(DataSourceId sourceId, DataSourceConfig config) {
|
||||
public DataSourceImpl(DataSourceId sourceId, DataSourceConfig config, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
this.sourceId = sourceId;
|
||||
this.config = config;
|
||||
this.internalSource = internalSource;
|
||||
}
|
||||
|
||||
public io.xpipe.core.source.DataSource<?> getInternalSource() {
|
||||
return internalSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,8 +9,8 @@ public class DataStructureImpl extends DataSourceImpl implements DataStructure {
|
|||
|
||||
private final DataSourceInfo.Structure info;
|
||||
|
||||
public DataStructureImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Structure info) {
|
||||
super(sourceId, sourceConfig);
|
||||
public DataStructureImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Structure info, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
|||
|
||||
private final DataSourceInfo.Table info;
|
||||
|
||||
DataTableImpl(DataSourceId id, DataSourceConfig sourceConfig, DataSourceInfo.Table info) {
|
||||
super(id, sourceConfig);
|
||||
DataTableImpl(DataSourceId id, DataSourceConfig sourceConfig, DataSourceInfo.Table info, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(id, sourceConfig, internalSource);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,33 @@ package io.xpipe.api.impl;
|
|||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataText;
|
||||
import io.xpipe.core.source.*;
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.beacon.BeaconConnection;
|
||||
import io.xpipe.beacon.BeaconException;
|
||||
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||
|
||||
private final DataSourceInfo.Text info;
|
||||
|
||||
public DataTextImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Text info) {
|
||||
super(sourceId, sourceConfig);
|
||||
public DataTextImpl(DataSourceId sourceId, DataSourceConfig sourceConfig, DataSourceInfo.Text info, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
|
@ -33,26 +49,80 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
|
|||
|
||||
@Override
|
||||
public List<String> readAllLines() {
|
||||
return null;
|
||||
return readLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> readLines(int maxLines) {
|
||||
return null;
|
||||
try (Stream<String> lines = lines()) {
|
||||
return lines.limit(maxLines).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> lines() {
|
||||
var iterator = new Iterator<String>() {
|
||||
|
||||
private final BeaconConnection connection;
|
||||
private final BufferedReader reader;
|
||||
private String nextValue;
|
||||
|
||||
{
|
||||
connection = XPipeConnection.open();
|
||||
var req = QueryTextDataExchange.Request.builder()
|
||||
.ref(DataSourceReference.id(getId())).maxLines(-1).build();
|
||||
connection.sendRequest(req);
|
||||
connection.receiveResponse();
|
||||
reader = new BufferedReader(new InputStreamReader(connection.receiveBody(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private void close() {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
connection.checkClosed();
|
||||
|
||||
try {
|
||||
nextValue = reader.readLine();
|
||||
} catch (IOException e) {
|
||||
throw new BeaconException(e);
|
||||
}
|
||||
return nextValue != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return nextValue;
|
||||
}
|
||||
};
|
||||
|
||||
return StreamSupport
|
||||
.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
|
||||
.onClose(iterator::close);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readAll() {
|
||||
return null;
|
||||
try (Stream<String> lines = lines()) {
|
||||
return lines.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(int maxCharacters) {
|
||||
return null;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
lines().takeWhile(s -> {
|
||||
if (builder.length() > maxCharacters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.append(s);
|
||||
return true;
|
||||
});
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,23 +6,24 @@ plugins {
|
|||
}
|
||||
|
||||
apply from: "$rootDir/deps/java.gradle"
|
||||
apply from: "$rootDir/deps/jackson.gradle"
|
||||
apply from: "$rootDir/deps/lombok.gradle"
|
||||
|
||||
configurations {
|
||||
compileOnly.extendsFrom(dep)
|
||||
dependencies {
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0"
|
||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0"
|
||||
}
|
||||
|
||||
version = file('../misc/version').text
|
||||
group = 'io.xpipe'
|
||||
archivesBaseName = 'beacon'
|
||||
archivesBaseName = 'xpipe-beacon'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
api project(':core')
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = project.archivesBaseName
|
||||
|
||||
from components.java
|
||||
|
||||
pom.withXml {
|
||||
def pomNode = asNode()
|
||||
pomNode.dependencies.'*'.findAll().each() {
|
||||
it.scope*.value = 'compile'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pom {
|
||||
name = 'X-Pipe Beacon'
|
||||
description = 'The socket-based implementation used for the communication with the X-Pipe daemon.'
|
||||
|
|
|
@ -102,11 +102,11 @@ public class BeaconClient implements AutoCloseable {
|
|||
var msg = JsonNodeFactory.instance.objectNode();
|
||||
msg.set("xPipeMessage", json);
|
||||
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
if (BeaconConfig.printMessages()) {
|
||||
System.out.println("Sending request to server of type " + req.getClass().getName());
|
||||
}
|
||||
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
if (BeaconConfig.printMessages()) {
|
||||
System.out.println("Sending raw request:");
|
||||
System.out.println(msg.toPrettyString());
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ public class BeaconClient implements AutoCloseable {
|
|||
throw new ConnectorException("Couldn't read from socket", ex);
|
||||
}
|
||||
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
if (BeaconConfig.printMessages()) {
|
||||
System.out.println("Received response:");
|
||||
System.out.println(read.toPrettyString());
|
||||
}
|
||||
|
|
|
@ -8,19 +8,39 @@ import java.nio.charset.StandardCharsets;
|
|||
public class BeaconConfig {
|
||||
|
||||
public static final byte[] BODY_SEPARATOR = "\n\n".getBytes(StandardCharsets.UTF_8);
|
||||
private static final String DEBUG_PROP = "io.xpipe.beacon.debugOutput";
|
||||
|
||||
public static boolean debugEnabled() {
|
||||
if (System.getProperty(DEBUG_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(DEBUG_PROP));
|
||||
private static final String PRINT_MESSAGES_PROPERTY = "io.xpipe.beacon.printMessages";
|
||||
|
||||
public static boolean printMessages() {
|
||||
if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(PRINT_MESSAGES_PROPERTY));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final String LAUNCH_DAEMON_IN_DEBUG_PROP = "io.xpipe.beacon.launchDebugDaemon";
|
||||
|
||||
public static boolean launchDaemonInDebugMode() {
|
||||
if (System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon";
|
||||
|
||||
public static boolean attachDebuggerToDaemon() {
|
||||
if (System.getProperty(ATTACH_DEBUGGER_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(ATTACH_DEBUGGER_PROP));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.debugExecOutput";
|
||||
|
||||
public static boolean execDebugEnabled() {
|
||||
private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
|
||||
|
||||
public static boolean printDaemonOutput() {
|
||||
if (System.getProperty(EXEC_DEBUG_PROP) != null) {
|
||||
return Boolean.parseBoolean(System.getProperty(EXEC_DEBUG_PROP));
|
||||
}
|
||||
|
@ -42,13 +62,25 @@ public class BeaconConfig {
|
|||
|
||||
|
||||
|
||||
private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.exec";
|
||||
private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand";
|
||||
|
||||
public static String getCustomExecCommand() {
|
||||
public static String getCustomDaemonCommand() {
|
||||
if (System.getProperty(EXEC_PROCESS_PROP) != null) {
|
||||
return System.getProperty(EXEC_PROCESS_PROP);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs";
|
||||
|
||||
public static String getDaemonArguments() {
|
||||
if (System.getProperty(DAEMON_ARGUMENTS_PROP) != null) {
|
||||
return System.getProperty(DAEMON_ARGUMENTS_PROP);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ public abstract class BeaconConnection implements AutoCloseable {
|
|||
|
||||
public <REQ extends RequestMessage, RES extends ResponseMessage> RES performOutputExchange(
|
||||
REQ req,
|
||||
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter) {
|
||||
BeaconClient.FailableConsumer<OutputStream, Exception> reqWriter) {
|
||||
checkClosed();
|
||||
|
||||
try {
|
||||
|
|
|
@ -36,7 +36,7 @@ public class BeaconFormat {
|
|||
}
|
||||
|
||||
private void finishBlock() throws IOException {
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
if (BeaconConfig.printMessages()) {
|
||||
System.out.println("Sending data block of length " + index);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class BeaconFormat {
|
|||
var length = in.readNBytes(4);
|
||||
var lengthInt = ByteBuffer.wrap(length).getInt();
|
||||
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
if (BeaconConfig.printMessages()) {
|
||||
System.out.println("Receiving data block of length " + lengthInt);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.beacon.exchange.StopExchange;
|
|||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -16,6 +15,14 @@ import java.util.Optional;
|
|||
@UtilityClass
|
||||
public class BeaconServer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (tryStartCustom() == null) {
|
||||
if (tryStart() == null) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isRunning() {
|
||||
try (var socket = new BeaconClient()) {
|
||||
return true;
|
||||
|
@ -24,26 +31,56 @@ public class BeaconServer {
|
|||
}
|
||||
}
|
||||
|
||||
private static void startFork(String custom) throws IOException {
|
||||
boolean print = BeaconConfig.execDebugEnabled();
|
||||
if (print) {
|
||||
System.out.println("Executing custom daemon launch command: " + custom);
|
||||
public static Process tryStartCustom() throws Exception {
|
||||
var custom = BeaconConfig.getCustomDaemonCommand();
|
||||
if (custom != null) {
|
||||
var command = custom + " " + (BeaconConfig.getDaemonArguments() != null ?
|
||||
BeaconConfig.getDaemonArguments() :
|
||||
"");
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
printDaemonOutput(process, command);
|
||||
return process;
|
||||
}
|
||||
var proc = Runtime.getRuntime().exec(custom);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Process tryStart() throws Exception {
|
||||
var daemonExecutable = getDaemonExecutable();
|
||||
if (daemonExecutable.isPresent()) {
|
||||
var command = "\"" + daemonExecutable.get() + "\" --external " + (BeaconConfig.getDaemonArguments() != null ?
|
||||
BeaconConfig.getDaemonArguments() :
|
||||
"");
|
||||
// Tell daemon that we launched from an external tool
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
printDaemonOutput(process, command);
|
||||
return process;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void printDaemonOutput(Process proc, String command) {
|
||||
boolean print = BeaconConfig.printDaemonOutput();
|
||||
if (print) {
|
||||
System.out.println("Starting daemon: " + command);
|
||||
}
|
||||
|
||||
if (!print) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(null, () -> {
|
||||
try {
|
||||
InputStreamReader isr = new InputStreamReader(proc.getInputStream());
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (print) {
|
||||
System.out.println("[xpiped] " + line);
|
||||
}
|
||||
System.out.println("[xpiped] " + line);
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}, "daemon fork sysout").start();
|
||||
}, "daemon sysout").start();
|
||||
|
||||
new Thread(null, () -> {
|
||||
try {
|
||||
|
@ -51,46 +88,12 @@ public class BeaconServer {
|
|||
BufferedReader br = new BufferedReader(isr);
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (print) {
|
||||
System.err.println("[xpiped] " + line);
|
||||
}
|
||||
}
|
||||
int exit = proc.waitFor();
|
||||
if (exit != 0) {
|
||||
System.err.println("Daemon launch command failed");
|
||||
System.err.println("[xpiped] " + line);
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}, "daemon fork syserr").start();
|
||||
}
|
||||
|
||||
public static boolean tryStartFork() throws Exception {
|
||||
var custom = BeaconConfig.getCustomExecCommand();
|
||||
if (custom != null) {
|
||||
System.out.println("Starting fork: " + custom);
|
||||
startFork(custom);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean tryStart() throws Exception {
|
||||
var daemonExecutable = getDaemonExecutable();
|
||||
if (daemonExecutable.isPresent()) {
|
||||
if (BeaconConfig.debugEnabled()) {
|
||||
System.out.println("Starting daemon executable: " + daemonExecutable.get());
|
||||
}
|
||||
|
||||
// Tell daemon that we launched from an external tool
|
||||
new ProcessBuilder(daemonExecutable.get().toString(), "--external")
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, "daemon syserr").start();
|
||||
}
|
||||
|
||||
public static boolean tryStop(BeaconClient client) throws Exception {
|
||||
|
@ -99,40 +102,59 @@ public class BeaconServer {
|
|||
return res.isSuccess();
|
||||
}
|
||||
|
||||
private static Optional<Path> getDaemonExecutableFromHome() {
|
||||
var env = System.getenv("XPIPE_HOME");
|
||||
if (env == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Path file;
|
||||
|
||||
private static Optional<Path> getDaemonBasePath() {
|
||||
Path base = null;
|
||||
// Prepare for invalid XPIPE_HOME path value
|
||||
try {
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
file = Path.of(env, "app", "xpiped.exe");
|
||||
} else {
|
||||
file = Path.of(env, "app", "bin", "xpiped");
|
||||
}
|
||||
return Files.exists(file) ? Optional.of(file) : Optional.empty();
|
||||
var environmentVariable = System.getenv("XPIPE_HOME");
|
||||
base = environmentVariable != null ? Path.of(environmentVariable) : null;
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
if (base == null) {
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
base = Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe");
|
||||
} else {
|
||||
base = Path.of("/opt/xpipe/");
|
||||
}
|
||||
if (!Files.exists(base)) {
|
||||
base = null;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(base);
|
||||
}
|
||||
|
||||
public static Optional<Path> getDaemonExecutable() {
|
||||
var home = getDaemonExecutableFromHome();
|
||||
if (home.isPresent()) {
|
||||
return home;
|
||||
}
|
||||
var base = getDaemonBasePath().orElseThrow();
|
||||
var debug = BeaconConfig.launchDaemonInDebugMode();
|
||||
Path executable = null;
|
||||
if (!debug) {
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
executable = Path.of("app", "xpiped.exe");
|
||||
} else {
|
||||
executable = Path.of("app/bin/xpiped");
|
||||
}
|
||||
|
||||
Path file;
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
file = Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe", "app", "xpiped.exe");
|
||||
} else {
|
||||
file = Path.of("/opt/xpipe/app/bin/xpiped");
|
||||
String scriptName = null;
|
||||
if (BeaconConfig.attachDebuggerToDaemon()) {
|
||||
scriptName = "xpiped_debug_attach";
|
||||
} else {
|
||||
scriptName = "xpiped_debug";
|
||||
}
|
||||
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
scriptName = scriptName + ".bat";
|
||||
} else {
|
||||
scriptName = scriptName + ".sh";
|
||||
}
|
||||
|
||||
executable = Path.of("app", "scripts", scriptName);
|
||||
}
|
||||
|
||||
Path file = base.resolve(executable);
|
||||
if (Files.exists(file)) {
|
||||
return Optional.of(file);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package io.xpipe.beacon.exchange;
|
||||
|
||||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class AddSourceExchange implements MessageExchange {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "addSource";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
DataSourceId target;
|
||||
@NonNull DataSource<?> source;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
@NonNull
|
||||
DataSourceId id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package io.xpipe.beacon.exchange;
|
||||
|
||||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
public class ForwardExchange implements MessageExchange {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "forward";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request implements RequestMessage {
|
||||
@NonNull
|
||||
DataSourceReference source;
|
||||
|
||||
@NonNull
|
||||
DataSourceReference target;
|
||||
|
||||
boolean append;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package io.xpipe.beacon.exchange;
|
|||
|
||||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataSourceInfo;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
|
@ -34,8 +35,10 @@ public class QueryDataSourceExchange implements MessageExchange {
|
|||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {
|
||||
@NonNull
|
||||
DataSourceId id;
|
||||
boolean disabled;
|
||||
boolean hidden;
|
||||
@NonNull
|
||||
DataSourceInfo info;
|
||||
@NonNull
|
||||
|
@ -44,5 +47,7 @@ public class QueryDataSourceExchange implements MessageExchange {
|
|||
String provider;
|
||||
@NonNull
|
||||
Map<String, String> config;
|
||||
|
||||
DataSource<?> internalSource;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ public class QueryTextDataExchange implements MessageExchange {
|
|||
@NonNull
|
||||
DataSourceReference ref;
|
||||
|
||||
int maxLines;
|
||||
@Builder.Default
|
||||
int maxLines = -1;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
|
|
|
@ -22,7 +22,9 @@ module io.xpipe.beacon {
|
|||
|
||||
uses MessageExchange;
|
||||
provides io.xpipe.beacon.exchange.MessageExchange with
|
||||
ForwardExchange,
|
||||
EditStoreExchange,
|
||||
AddSourceExchange,
|
||||
StoreProviderListExchange,
|
||||
ListCollectionsExchange,
|
||||
ListEntriesExchange,
|
||||
|
|
|
@ -17,15 +17,11 @@ configurations {
|
|||
|
||||
version = file('../misc/version').text
|
||||
group = 'io.xpipe'
|
||||
archivesBaseName = 'core'
|
||||
archivesBaseName = 'xpipe-core'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies{
|
||||
compileOnly 'org.apache.commons:commons-exec:1.3'
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
apply from: "$rootDir/deps/publish-base.gradle"
|
|
@ -1,6 +1,8 @@
|
|||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = project.archivesBaseName
|
||||
|
||||
from components.java
|
||||
|
||||
pom {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package io.xpipe.core.charsetter;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public interface Charsettable {
|
||||
|
||||
Charset getCharset();
|
||||
StreamCharset getCharset();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package io.xpipe.core.charsetter;
|
||||
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -27,11 +29,12 @@ public abstract class Charsetter {
|
|||
|
||||
@Value
|
||||
public static class Result {
|
||||
Charset charset;
|
||||
StreamCharset charset;
|
||||
NewLine newLine;
|
||||
}
|
||||
|
||||
protected Charsetter() {}
|
||||
protected Charsetter() {
|
||||
}
|
||||
|
||||
public static Charsetter INSTANCE;
|
||||
|
||||
|
@ -50,8 +53,77 @@ public abstract class Charsetter {
|
|||
void accept(T var1) throws E;
|
||||
}
|
||||
|
||||
public BufferedReader reader(StreamDataStore store, StreamCharset charset) throws Exception {
|
||||
return reader(store.openBufferedInput(), charset);
|
||||
}
|
||||
|
||||
public OutputStreamWriter writer(StreamDataStore store, StreamCharset charset) throws Exception {
|
||||
var out = new OutputStreamWriter(store.openOutput(), charset.getCharset());
|
||||
return out;
|
||||
}
|
||||
|
||||
public BufferedReader reader(InputStream stream, StreamCharset charset) throws Exception {
|
||||
if (charset.hasByteOrderMark()) {
|
||||
var bom = stream.readNBytes(charset.getByteOrderMark().length);
|
||||
if (bom.length != 0 && !Arrays.equals(bom, charset.getByteOrderMark())) {
|
||||
throw new IllegalStateException("Invalid charset: " + toString());
|
||||
}
|
||||
}
|
||||
|
||||
return new BufferedReader(new InputStreamReader(stream, charset.getCharset()));
|
||||
}
|
||||
|
||||
public abstract Result read(FailableSupplier<InputStream, Exception> in, FailableConsumer<InputStreamReader, Exception> con) throws Exception;
|
||||
|
||||
private static final int MAX_BYTES = 8192;
|
||||
|
||||
public Result detect(StreamDataStore store) throws Exception {
|
||||
Result result = new Result(null, null);
|
||||
|
||||
if (store.canOpen()) {
|
||||
|
||||
|
||||
try (InputStream inputStream = store.openBufferedInput()) {
|
||||
StreamCharset detected = null;
|
||||
for (var charset : StreamCharset.COMMON) {
|
||||
if (charset.hasByteOrderMark()) {
|
||||
inputStream.mark(charset.getByteOrderMark().length);
|
||||
var bom = inputStream.readNBytes(charset.getByteOrderMark().length);
|
||||
inputStream.reset();
|
||||
if (Arrays.equals(bom, charset.getByteOrderMark())) {
|
||||
detected = charset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = inputStream.readNBytes(MAX_BYTES);
|
||||
if (detected == null) {
|
||||
detected = StreamCharset.get(inferCharset(bytes), false);
|
||||
}
|
||||
var nl = inferNewLine(bytes);
|
||||
result = new Result(detected, nl);
|
||||
}
|
||||
}
|
||||
|
||||
if (store instanceof FileStore fileStore) {
|
||||
var newline = fileStore.getMachine().getNewLine();
|
||||
if (result.getNewLine() == null) {
|
||||
result = new Result(result.getCharset(), newline);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.getCharset() == null) {
|
||||
result = new Result(StreamCharset.UTF8, result.getNewLine());
|
||||
}
|
||||
|
||||
if (result.getNewLine() == null) {
|
||||
result = new Result(result.getCharset(), NewLine.platform());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public NewLine inferNewLine(byte[] content) {
|
||||
Map<NewLine, Integer> count = new HashMap<>();
|
||||
for (var nl : NewLine.values()) {
|
||||
|
@ -69,10 +141,10 @@ public abstract class Charsetter {
|
|||
|
||||
private static int count(byte[] outerArray, byte[] smallerArray) {
|
||||
int count = 0;
|
||||
for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
|
||||
for (int i = 0; i < outerArray.length - smallerArray.length + 1; ++i) {
|
||||
boolean found = true;
|
||||
for(int j = 0; j < smallerArray.length; ++j) {
|
||||
if (outerArray[i+j] != smallerArray[j]) {
|
||||
for (int j = 0; j < smallerArray.length; ++j) {
|
||||
if (outerArray[i + j] != smallerArray[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package io.xpipe.core.charsetter;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Value
|
||||
public class StreamCharset {
|
||||
|
||||
public static StreamCharset get(Charset charset, boolean byteOrderMark) {
|
||||
return Stream.concat(COMMON.stream(), RARE.stream())
|
||||
.filter(streamCharset -> streamCharset.getCharset()
|
||||
.equals(charset) && streamCharset.hasByteOrderMark() == byteOrderMark)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static StreamCharset get(String s) {
|
||||
var byteOrderMark = s.endsWith("-bom");
|
||||
var charset = Charset.forName(s.substring(
|
||||
0, s.length() - (byteOrderMark ?
|
||||
4 :
|
||||
0)));
|
||||
return StreamCharset.get(charset, byteOrderMark);
|
||||
}
|
||||
|
||||
Charset charset;
|
||||
byte[] byteOrderMark;
|
||||
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return getCharset()
|
||||
.name().toLowerCase(Locale.ROOT) + (hasByteOrderMark() ?
|
||||
"-bom" :
|
||||
"");
|
||||
}
|
||||
|
||||
public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null);
|
||||
public static final StreamCharset UTF8_BOM = new StreamCharset(StandardCharsets.UTF_8, new byte[]{
|
||||
(byte) 0xEF,
|
||||
(byte) 0xBB,
|
||||
(byte) 0xBF
|
||||
});
|
||||
|
||||
public static final StreamCharset UTF16 = new StreamCharset(StandardCharsets.UTF_16, null);
|
||||
public static final StreamCharset UTF16_BOM = new StreamCharset(StandardCharsets.UTF_16, new byte[]{
|
||||
(byte) 0xFE,
|
||||
(byte) 0xFF
|
||||
});
|
||||
|
||||
public static final StreamCharset UTF16_LE = new StreamCharset(StandardCharsets.UTF_16LE, null);
|
||||
public static final StreamCharset UTF16_LE_BOM = new StreamCharset(StandardCharsets.UTF_16LE, new byte[]{
|
||||
(byte) 0xFF,
|
||||
(byte) 0xFE
|
||||
});
|
||||
|
||||
public static final StreamCharset UTF32 = new StreamCharset(Charset.forName("utf-32"), null);
|
||||
public static final StreamCharset UTF32_BOM = new StreamCharset(Charset.forName("utf-32"), new byte[]{
|
||||
0x00,
|
||||
0x00,
|
||||
(byte) 0xFE,
|
||||
(byte) 0xFF
|
||||
});
|
||||
|
||||
public static final List<StreamCharset> COMMON = List.of(
|
||||
UTF8, UTF8_BOM, UTF16, UTF16_BOM, UTF16_LE, UTF16_LE_BOM, UTF32, UTF32_BOM, new StreamCharset(StandardCharsets.US_ASCII, null),
|
||||
new StreamCharset(StandardCharsets.ISO_8859_1, null),
|
||||
new StreamCharset(Charset.forName("Windows-1251"), null), new StreamCharset(Charset.forName("Windows-1252"), null)
|
||||
);
|
||||
|
||||
public static final List<StreamCharset> RARE = Charset.availableCharsets()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(charset -> COMMON.stream()
|
||||
.noneMatch(c -> c.getCharset()
|
||||
.equals(charset)))
|
||||
.map(charset -> new StreamCharset(charset, null))
|
||||
.toList();
|
||||
|
||||
public boolean hasByteOrderMark() {
|
||||
return byteOrderMark != null;
|
||||
}
|
||||
}
|
|
@ -2,15 +2,25 @@ package io.xpipe.core.data.node;
|
|||
|
||||
import io.xpipe.core.data.type.DataType;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||
|
||||
public static final String KEY_TABLE_NAME = "tableName";
|
||||
public static final String KEY_ROW_NAME = "rowName";
|
||||
|
||||
private Properties properties = new Properties();
|
||||
|
||||
public String getMetaString(String key) {
|
||||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
public abstract DataStructureNode mutableCopy();
|
||||
|
||||
public String keyNameAt(int index) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.core.data.node;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class SimpleImmutableValueNode extends ImmutableValueNode {
|
||||
|
||||
private final byte[] data;
|
||||
|
@ -30,6 +32,6 @@ public class SimpleImmutableValueNode extends ImmutableValueNode {
|
|||
return "null";
|
||||
}
|
||||
|
||||
return new String(getRawData());
|
||||
return new String(getRawData(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ public class TypedDataStreamParser {
|
|||
|
||||
public DataStructureNode parseStructure(InputStream in, TypedAbstractReader cb) throws IOException {
|
||||
if (!hasNext(in)) {
|
||||
throw new IllegalStateException("No structure to read");
|
||||
return null;
|
||||
}
|
||||
|
||||
cb.onNodeBegin();
|
||||
|
|
|
@ -29,6 +29,11 @@ public class BaseQueryElement extends DialogElement {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresExplicitUserInput() {
|
||||
return required && value == null;
|
||||
}
|
||||
|
||||
public boolean isNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ public class ChoiceElement extends DialogElement {
|
|||
|
||||
private int selected;
|
||||
|
||||
@Override
|
||||
public boolean requiresExplicitUserInput() {
|
||||
return required && selected == -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return description;
|
||||
|
|
|
@ -126,6 +126,11 @@ public abstract class Dialog {
|
|||
|
||||
@Override
|
||||
protected DialogElement next(String answer) throws Exception {
|
||||
if (element.requiresExplicitUserInput() && (answer == null || answer.trim()
|
||||
.length() == 0)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (element.apply(answer)) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ public abstract class DialogElement {
|
|||
|
||||
public abstract String toDisplayString();
|
||||
|
||||
public boolean requiresExplicitUserInput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean apply(String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -1,24 +1,38 @@
|
|||
package io.xpipe.core.dialog;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
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> {
|
||||
|
||||
public static final QueryConverter<Charset> CHARSET = new QueryConverter<Charset>() {
|
||||
public static final QueryConverter<NewLine> NEW_LINE = new QueryConverter<NewLine>() {
|
||||
@Override
|
||||
protected Charset fromString(String s) {
|
||||
return Charset.forName(s);
|
||||
protected NewLine fromString(String s) {
|
||||
return NewLine.id(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Charset value) {
|
||||
return value.name();
|
||||
protected String toString(NewLine value) {
|
||||
return value.getId();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final QueryConverter<StreamCharset> CHARSET = new QueryConverter<StreamCharset>() {
|
||||
@Override
|
||||
protected StreamCharset fromString(String s) {
|
||||
return StreamCharset.get(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(StreamCharset value) {
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -127,7 +141,9 @@ public abstract class QueryConverter<T> {
|
|||
|
||||
@Override
|
||||
protected String toString(Boolean value) {
|
||||
return value ? "yes" : "no";
|
||||
return value ?
|
||||
"yes" :
|
||||
"no";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -14,12 +14,11 @@ public class QueryElement extends BaseQueryElement {
|
|||
|
||||
@Override
|
||||
public boolean apply(String value) {
|
||||
if (value == null && this.value != null) {
|
||||
if (value == null) {
|
||||
if (isRequired() && this.value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,12 +38,12 @@ public abstract class DataSource<DS extends DataStore> {
|
|||
return c;
|
||||
}
|
||||
|
||||
protected boolean supportsRead() {
|
||||
return false;
|
||||
public boolean supportsRead() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean supportsWrite() {
|
||||
return false;
|
||||
public boolean supportsWrite() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
import io.xpipe.core.charsetter.Charsetter;
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
public abstract class StreamReadConnection implements DataSourceReadConnection {
|
||||
|
||||
protected InputStream inputStream;
|
||||
protected Reader reader;
|
||||
private final StreamCharset charset;
|
||||
protected final StreamDataStore store;
|
||||
|
||||
public StreamReadConnection(StreamDataStore store, StreamCharset charset) {
|
||||
this.store = store;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
if (inputStream != null) {
|
||||
throw new IllegalStateException("Already initialized");
|
||||
}
|
||||
|
||||
inputStream = store.openInput();
|
||||
if (charset != null) {
|
||||
reader = Charsetter.get().reader(inputStream, charset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (inputStream == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
public class StreamWriteConnection implements DataSourceConnection {
|
||||
|
||||
protected final StreamDataStore store;
|
||||
protected OutputStream outputStream;
|
||||
private final StreamCharset charset;
|
||||
protected OutputStreamWriter writer;
|
||||
|
||||
public StreamWriteConnection(StreamDataStore store, StreamCharset charset) {
|
||||
this.store = store;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
if (outputStream != null) {
|
||||
throw new IllegalStateException("Already initialized");
|
||||
}
|
||||
|
||||
outputStream = store.openOutput();
|
||||
if (charset != null) {
|
||||
if (charset.hasByteOrderMark()) {
|
||||
outputStream
|
||||
.write(charset.getByteOrderMark());
|
||||
}
|
||||
writer = new OutputStreamWriter(outputStream, charset.getCharset());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (outputStream == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
|
@ -8,6 +9,7 @@ import io.xpipe.core.data.type.TupleType;
|
|||
import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
|
@ -57,7 +59,17 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
|||
/**
|
||||
* Reads multiple rows in bulk.
|
||||
*/
|
||||
ArrayNode readRows(int maxLines) throws Exception;
|
||||
default ArrayNode readRows(int maxLines) throws Exception{
|
||||
var list = new ArrayList<DataStructureNode>();
|
||||
|
||||
AtomicInteger rowCounter = new AtomicInteger();
|
||||
withRows(t -> {
|
||||
list.add(t);
|
||||
rowCounter.getAndIncrement();
|
||||
return rowCounter.get() != maxLines;
|
||||
});
|
||||
return ArrayNode.of(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the rows to an OutputStream in the X-Pipe binary format.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
|
@ -11,5 +12,10 @@ public interface TableWriteConnection extends DataSourceConnection {
|
|||
|
||||
DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor();
|
||||
|
||||
void writeLines(ArrayNode lines) throws Exception;
|
||||
default void writeLines(ArrayNode lines) throws Exception{
|
||||
var consumer = writeLinesAcceptor();
|
||||
for (DataStructureNode dataStructureNode : lines.getNodes()) {
|
||||
consumer.accept(dataStructureNode.asTuple());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS
|
|||
|
||||
@Override
|
||||
public final DataSourceInfo determineInfo() throws Exception {
|
||||
if (!getStore().canOpen()) {
|
||||
return new DataSourceInfo.Text(-1, -1);
|
||||
}
|
||||
|
||||
try (var con = openReadConnection()) {
|
||||
AtomicInteger lineCount = new AtomicInteger();
|
||||
AtomicInteger charCount = new AtomicInteger();
|
||||
|
@ -42,7 +46,16 @@ public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS
|
|||
return con;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final TextWriteConnection openAppendingWriteConnection() throws Exception {
|
||||
var con = newAppendingWriteConnection();
|
||||
con.init();
|
||||
return con;
|
||||
}
|
||||
|
||||
|
||||
protected abstract TextWriteConnection newWriteConnection();
|
||||
protected abstract TextWriteConnection newAppendingWriteConnection();
|
||||
|
||||
protected abstract TextReadConnection newReadConnection();
|
||||
}
|
||||
|
|
|
@ -1,36 +1,22 @@
|
|||
package io.xpipe.core.source;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface TextReadConnection extends DataSourceReadConnection {
|
||||
|
||||
Stream<String> lines() throws Exception;
|
||||
|
||||
boolean isFinished() throws Exception;
|
||||
|
||||
default void forwardLines(OutputStream out, int maxLines) throws Exception {
|
||||
if (maxLines == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
for (var it = lines().iterator(); it.hasNext(); counter++) {
|
||||
if (counter == maxLines) {
|
||||
break;
|
||||
}
|
||||
|
||||
out.write(it.next().getBytes(StandardCharsets.UTF_8));
|
||||
out.write("\n".getBytes(StandardCharsets.UTF_8));
|
||||
default String readAll() throws Exception {
|
||||
try (Stream<String> lines = lines()) {
|
||||
return lines.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
default void forward(DataSourceConnection con) throws Exception {
|
||||
try (var tCon = (TextWriteConnection) con) {
|
||||
var tCon = (TextWriteConnection) con;
|
||||
for (var it = lines().iterator(); it.hasNext(); ) {
|
||||
tCon.writeLine(it.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,30 @@ import java.util.Optional;
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public interface DataStore {
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this store can be opened.
|
||||
* This can be not the case for example if the underlying store does not exist.
|
||||
*/
|
||||
default boolean canOpen() throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean canWrite() throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this data store can only be accessed by the current running application.
|
||||
* One example are standard in and standard out stores.
|
||||
*
|
||||
* @see StdinDataStore
|
||||
* @see StdoutDataStore
|
||||
*/
|
||||
default boolean isLocalToApplication() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a validation of this data store.
|
||||
*
|
||||
|
|
|
@ -75,4 +75,5 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
|||
}
|
||||
return split[split.length - 1];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,5 +15,14 @@ public interface FilenameStore extends DataStore {
|
|||
return Optional.of(i != -1 ? n.substring(0, i) : n);
|
||||
}
|
||||
|
||||
|
||||
default String getFileExtension() {
|
||||
var split = getFileName().split("[\\\\.]");
|
||||
if (split.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return split[split.length - 1];
|
||||
}
|
||||
|
||||
String getFileName();
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package io.xpipe.core.store;
|
|||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.NonFinal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A store whose contents are stored in memory.
|
||||
|
@ -14,8 +14,13 @@ import java.io.InputStream;
|
|||
@JsonTypeName("inMemory")
|
||||
public class InMemoryStore implements StreamDataStore {
|
||||
|
||||
@NonFinal
|
||||
byte[] value;
|
||||
|
||||
public InMemoryStore() {
|
||||
this.value = new byte[0];
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public InMemoryStore(byte[] value) {
|
||||
this.value = value;
|
||||
|
@ -31,6 +36,17 @@ public class InMemoryStore implements StreamDataStore {
|
|||
return new ByteArrayInputStream(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutput() throws Exception {
|
||||
return new ByteArrayOutputStream(){
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
InMemoryStore.this.value = this.toByteArray();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSummaryString() {
|
||||
return "inMemory";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.util.Secret;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -97,6 +98,17 @@ public class LocalStore extends StandardShellStore implements MachineFileStore {
|
|||
return Files.exists(Path.of(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdirs(String file) throws Exception {
|
||||
Files.createDirectories(Path.of(file).getParent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewLine getNewLine() {
|
||||
return ShellTypes.getDefault().getNewLine();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toSummaryString() {
|
||||
return "localhost";
|
||||
|
@ -110,6 +122,7 @@ public class LocalStore extends StandardShellStore implements MachineFileStore {
|
|||
|
||||
@Override
|
||||
public OutputStream openOutput(String file) throws Exception {
|
||||
mkdirs(file);
|
||||
var p = Path.of(file);
|
||||
return Files.newOutputStream(p);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
@ -15,4 +17,8 @@ public interface MachineFileStore extends DataStore {
|
|||
OutputStream openOutput(String file) throws Exception;
|
||||
|
||||
public boolean exists(String file) throws Exception;
|
||||
|
||||
void mkdirs(String file) throws Exception;
|
||||
|
||||
NewLine getNewLine() throws Exception;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.util.Secret;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -63,6 +64,11 @@ public class ShellTypes {
|
|||
return StandardCharsets.UTF_16LE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewLine getNewLine() {
|
||||
return NewLine.CRLF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "powershell";
|
||||
|
@ -82,6 +88,11 @@ public class ShellTypes {
|
|||
@JsonProperty("cmd")
|
||||
public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() {
|
||||
|
||||
@Override
|
||||
public NewLine getNewLine() {
|
||||
return NewLine.CRLF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> switchTo(List<String> cmd) {
|
||||
var l = new ArrayList<>(cmd);
|
||||
|
@ -169,6 +180,11 @@ public class ShellTypes {
|
|||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewLine getNewLine() {
|
||||
return NewLine.LF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sh";
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.util.Secret;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
@ -26,6 +27,8 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
|
|||
|
||||
Charset getCharset();
|
||||
|
||||
NewLine getNewLine();
|
||||
|
||||
String getName();
|
||||
|
||||
String getDisplayName();
|
||||
|
@ -33,6 +36,11 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
|
|||
List<String> getOperatingSystemNameCommand();
|
||||
}
|
||||
|
||||
|
||||
public NewLine getNewLine() throws Exception {
|
||||
return determineType().getNewLine();
|
||||
}
|
||||
|
||||
public abstract ShellType determineType() throws Exception;
|
||||
|
||||
public final String querySystemName() throws Exception {
|
||||
|
@ -67,4 +75,9 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
|
|||
p.start();
|
||||
return p.waitFor() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdirs(String file) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@ import java.io.OutputStream;
|
|||
@JsonTypeName("stdin")
|
||||
public class StdinDataStore implements StreamDataStore {
|
||||
|
||||
@Override
|
||||
public boolean isLocalToApplication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInput() throws Exception {
|
||||
var in = System.in;
|
||||
|
|
|
@ -12,6 +12,11 @@ import java.io.OutputStream;
|
|||
@JsonTypeName("stdout")
|
||||
public class StdoutDataStore implements StreamDataStore {
|
||||
|
||||
@Override
|
||||
public boolean isLocalToApplication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutput() throws Exception {
|
||||
// Create an output stream that will write to standard out but will not close it
|
||||
|
|
|
@ -9,16 +9,6 @@ import java.io.OutputStream;
|
|||
*/
|
||||
public interface StreamDataStore extends DataStore {
|
||||
|
||||
/**
|
||||
* Indicates whether this data store can only be accessed by the current running application.
|
||||
* One example are standard in and standard out stores.
|
||||
*
|
||||
* @see StdinDataStore
|
||||
* @see StdoutDataStore
|
||||
*/
|
||||
default boolean isLocalToApplication() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an input stream that can be used to read its data.
|
||||
|
@ -46,13 +36,6 @@ public interface StreamDataStore extends DataStore {
|
|||
throw new UnsupportedOperationException("Can't open store output");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this store can be opened.
|
||||
* This can be not the case for example if the underlying store does not exist.
|
||||
*/
|
||||
default boolean canOpen() throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this store is persistent, i.e. whether the stored data can be read again or not.
|
||||
|
|
|
@ -12,26 +12,33 @@ import java.util.ServiceLoader;
|
|||
|
||||
public class JacksonHelper {
|
||||
|
||||
private static final ObjectMapper INSTANCE = new ObjectMapper();
|
||||
private static final ObjectMapper BASE = new ObjectMapper();
|
||||
private static ObjectMapper INSTANCE = new ObjectMapper();
|
||||
private static boolean init = false;
|
||||
private static List<Module> MODULES;
|
||||
|
||||
public static synchronized void initClassBased() {
|
||||
initModularized(null);
|
||||
}
|
||||
|
||||
public static synchronized void initModularized(ModuleLayer layer) {
|
||||
ObjectMapper objectMapper = INSTANCE;
|
||||
MODULES = findModules(layer);
|
||||
|
||||
ObjectMapper objectMapper = BASE;
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
objectMapper.registerModules(findModules(layer));
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
|
||||
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
|
||||
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
|
||||
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
|
||||
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
|
||||
|
||||
INSTANCE = BASE.copy();
|
||||
INSTANCE.registerModules(MODULES);
|
||||
|
||||
init = true;
|
||||
}
|
||||
|
||||
|
@ -56,6 +63,16 @@ public class JacksonHelper {
|
|||
return INSTANCE.copy();
|
||||
}
|
||||
|
||||
public static ObjectMapper newMapper(Class<?> excludedModule) {
|
||||
if (!init) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
var mapper = BASE.copy();
|
||||
mapper.registerModules(MODULES.stream().filter(module -> !module.getClass().equals(excludedModule)).toList());
|
||||
return mapper;
|
||||
}
|
||||
|
||||
public static boolean isInit() {
|
||||
return init;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import io.xpipe.core.util.CoreJacksonModule;
|
||||
|
||||
module io.xpipe.core {
|
||||
open module io.xpipe.core {
|
||||
exports io.xpipe.core.store;
|
||||
exports io.xpipe.core.source;
|
||||
exports io.xpipe.core.data.generic;
|
||||
|
@ -11,15 +11,6 @@ module io.xpipe.core {
|
|||
exports io.xpipe.core.dialog;
|
||||
exports io.xpipe.core.charsetter;
|
||||
|
||||
opens io.xpipe.core.store;
|
||||
opens io.xpipe.core.source;
|
||||
opens io.xpipe.core.data.type;
|
||||
opens io.xpipe.core.data.generic;
|
||||
opens io.xpipe.core.util;
|
||||
opens io.xpipe.core.data.node;
|
||||
opens io.xpipe.core.data.typed;
|
||||
opens io.xpipe.core.dialog;
|
||||
|
||||
requires static com.fasterxml.jackson.core;
|
||||
requires static com.fasterxml.jackson.databind;
|
||||
requires java.net.http;
|
||||
|
|
|
@ -8,12 +8,7 @@ plugins {
|
|||
apply from: "$rootDir/deps/java.gradle"
|
||||
apply from: "$rootDir/deps/javafx.gradle"
|
||||
apply from: "$rootDir/deps/richtextfx.gradle"
|
||||
apply from: "$rootDir/deps/preferencesfx.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: "$rootDir/deps/slf4j.gradle"
|
||||
|
||||
configurations {
|
||||
compileOnly.extendsFrom(dep)
|
||||
|
@ -21,16 +16,20 @@ configurations {
|
|||
|
||||
version = file('../misc/version').text
|
||||
group = 'io.xpipe'
|
||||
archivesBaseName = 'extension'
|
||||
archivesBaseName = 'xpipe-extension'
|
||||
|
||||
dependencies {
|
||||
compileOnly 'net.synedra:validatorfx:0.3.1'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
||||
implementation project(':core')
|
||||
api project(':core')
|
||||
api project(':beacon')
|
||||
api project(':api')
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "2.13.0"
|
||||
|
||||
// implementation project(':fxcomps')
|
||||
implementation 'io.xpipe:fxcomps:0.2.1'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||
implementation 'net.synedra:validatorfx:0.3.1'
|
||||
implementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
implementation 'com.jfoenix:jfoenix:9.0.10'
|
||||
implementation 'io.xpipe:fxcomps:0.2.2'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.1'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = project.archivesBaseName
|
||||
|
||||
from components.java
|
||||
|
||||
pom {
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -21,20 +19,21 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
}
|
||||
|
||||
default void validate() throws Exception {
|
||||
getGeneralType();
|
||||
getCategory();
|
||||
getSourceClass();
|
||||
}
|
||||
|
||||
default Category getGeneralType() {
|
||||
@SneakyThrows
|
||||
default T create(Object... arguments) {
|
||||
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(arguments);
|
||||
}
|
||||
|
||||
default Category getCategory() {
|
||||
if (getFileProvider() != null) {
|
||||
return Category.FILE;
|
||||
}
|
||||
|
||||
if (getDatabaseProvider() != null) {
|
||||
return Category.DATABASE;
|
||||
}
|
||||
|
||||
throw new ExtensionException("Provider has no general type");
|
||||
throw new ExtensionException("Provider has no set general type");
|
||||
}
|
||||
|
||||
default boolean supportsConversion(T in, DataSourceType t) {
|
||||
|
@ -49,14 +48,14 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
}
|
||||
|
||||
default String i18n(String key) {
|
||||
return I18n.get(getId() + "." + key);
|
||||
return I18n.get(i18nKey(key));
|
||||
}
|
||||
|
||||
default String i18nKey(String key) {
|
||||
return getId() + "." + key;
|
||||
}
|
||||
|
||||
default Region createConfigGui(Property<T> source, Property<T> appliedSource) {
|
||||
default Region configGui(Property<T> source, Property<T> appliedSource, boolean all) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -83,33 +82,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
Map<String, List<String>> getFileExtensions();
|
||||
}
|
||||
|
||||
interface DatabaseProvider {
|
||||
|
||||
}
|
||||
|
||||
public static Dialog charset(Charset c, boolean all) {
|
||||
return Dialog.query("charset", false, false, c != null &&!all, c, QueryConverter.CHARSET);
|
||||
}
|
||||
|
||||
public static Dialog newLine(NewLine l, boolean all) {
|
||||
return Dialog.query("newline", false, false, l != null &&!all, l, NEW_LINE_CONVERTER);
|
||||
}
|
||||
|
||||
static <T> Dialog query(String desc, T value, boolean required, QueryConverter<T> c, boolean all) {
|
||||
return Dialog.query(desc, false, required, value != null && !all, value, c);
|
||||
}
|
||||
|
||||
public static final QueryConverter<NewLine> NEW_LINE_CONVERTER = new QueryConverter<NewLine>() {
|
||||
@Override
|
||||
protected NewLine fromString(String s) {
|
||||
return NewLine.id(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(NewLine value) {
|
||||
return value.getId();
|
||||
}
|
||||
};
|
||||
|
||||
Dialog configDialog(T source, boolean all);
|
||||
|
||||
|
@ -123,7 +96,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
* Checks whether this provider prefers a certain kind of store.
|
||||
* This is important for the correct autodetection of a store.
|
||||
*/
|
||||
boolean prefersStore(DataStore store);
|
||||
boolean prefersStore(DataStore store, DataSourceType type);
|
||||
|
||||
/**
|
||||
* Checks whether this provider supports the store in principle.
|
||||
|
@ -147,23 +120,10 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
return null;
|
||||
}
|
||||
|
||||
default DatabaseProvider getDatabaseProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean hasDirectoryProvider() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default String getId() {
|
||||
return getPossibleNames().get(0);
|
||||
}
|
||||
|
||||
default String getModuleName() {
|
||||
var n = getClass().getPackageName();
|
||||
var i = n.lastIndexOf('.');
|
||||
return i != -1 ? n.substring(i + 1) : n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create a useful data source descriptor from a data store.
|
||||
|
@ -171,10 +131,6 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
*/
|
||||
T createDefaultSource(DataStore input) throws Exception;
|
||||
|
||||
default T createDefaultWriteSource(DataStore input) throws Exception {
|
||||
return createDefaultSource(input);
|
||||
}
|
||||
|
||||
Class<T> getSourceClass();
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.core.store.FileStore;
|
|||
import io.xpipe.extension.event.ErrorEvent;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
@ -17,15 +18,18 @@ public class DataSourceProviders {
|
|||
|
||||
public static void init(ModuleLayer layer) {
|
||||
if (ALL == null) {
|
||||
ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream()
|
||||
.map(p -> (DataSourceProvider<?>) p.get()).collect(Collectors.toSet());
|
||||
ALL = ServiceLoader.load(layer, DataSourceProvider.class)
|
||||
.stream()
|
||||
.map(p -> (DataSourceProvider<?>) p.get())
|
||||
.collect(Collectors.toSet());
|
||||
ALL.removeIf(p -> {
|
||||
try {
|
||||
p.init();
|
||||
p.validate();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
ErrorEvent.fromThrowable(e)
|
||||
.handle();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
@ -51,7 +55,8 @@ public class DataSourceProviders {
|
|||
@SneakyThrows
|
||||
public static StructureDataSource<FileStore> createLocalStructureDescriptor(DataStore store) {
|
||||
return (StructureDataSource<FileStore>)
|
||||
DataSourceProviders.byId("xpbs").getSourceClass()
|
||||
DataSourceProviders.byId("xpbs")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0].newInstance(store);
|
||||
}
|
||||
|
||||
|
@ -59,7 +64,8 @@ public class DataSourceProviders {
|
|||
@SneakyThrows
|
||||
public static RawDataSource<FileStore> createLocalRawDescriptor(DataStore store) {
|
||||
return (RawDataSource<FileStore>)
|
||||
DataSourceProviders.byId("binary").getSourceClass()
|
||||
DataSourceProviders.byId("binary")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0].newInstance(store);
|
||||
}
|
||||
|
||||
|
@ -67,7 +73,8 @@ public class DataSourceProviders {
|
|||
@SneakyThrows
|
||||
public static RawDataSource<FileStore> createLocalCollectionDescriptor(DataStore store) {
|
||||
return (RawDataSource<FileStore>)
|
||||
DataSourceProviders.byId("br").getSourceClass()
|
||||
DataSourceProviders.byId("br")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0].newInstance(store);
|
||||
}
|
||||
|
||||
|
@ -75,7 +82,8 @@ public class DataSourceProviders {
|
|||
@SneakyThrows
|
||||
public static TextDataSource<FileStore> createLocalTextDescriptor(DataStore store) {
|
||||
return (TextDataSource<FileStore>)
|
||||
DataSourceProviders.byId("text").getSourceClass()
|
||||
DataSourceProviders.byId("text")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0].newInstance(store);
|
||||
}
|
||||
|
||||
|
@ -83,7 +91,8 @@ public class DataSourceProviders {
|
|||
@SneakyThrows
|
||||
public static TableDataSource<FileStore> createLocalTableDescriptor(DataStore store) {
|
||||
return (TableDataSource<FileStore>)
|
||||
DataSourceProviders.byId("xpbt").getSourceClass()
|
||||
DataSourceProviders.byId("xpbt")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0].newInstance(store);
|
||||
}
|
||||
|
||||
|
@ -93,7 +102,10 @@ public class DataSourceProviders {
|
|||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return (T) ALL.stream().filter(d -> d.getId().equals(name)).findAny()
|
||||
return (T) ALL.stream()
|
||||
.filter(d -> d.getId()
|
||||
.equals(name))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Provider " + name + " not found"));
|
||||
}
|
||||
|
||||
|
@ -104,7 +116,10 @@ public class DataSourceProviders {
|
|||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return (T) ALL.stream().filter(d -> d.getSourceClass().equals(c)).findAny()
|
||||
return (T) ALL.stream()
|
||||
.filter(d -> d.getSourceClass()
|
||||
.equals(c))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Provider for " + c.getSimpleName() + " not found"));
|
||||
}
|
||||
|
||||
|
@ -113,17 +128,37 @@ public class DataSourceProviders {
|
|||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream().filter(d -> d.getPossibleNames().stream()
|
||||
.anyMatch(s -> s.equalsIgnoreCase(name))).findAny();
|
||||
return ALL.stream()
|
||||
.filter(d -> d.getPossibleNames()
|
||||
.stream()
|
||||
.anyMatch(s -> nameAlternatives(s).stream().anyMatch(s1 -> s1.equalsIgnoreCase(name))) || d.getId().equalsIgnoreCase(name))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider<?>> byPreferredStore(DataStore store) {
|
||||
private static List<String> nameAlternatives(String name) {
|
||||
var split = List.of(name.split("_"));
|
||||
return List.of(String.join(" ", split), String.join(
|
||||
"_", split
|
||||
), String.join(
|
||||
"-", split
|
||||
), split.stream()
|
||||
.map(s -> s.equals(split.get(0)) ?
|
||||
s :
|
||||
s.substring(0, 1)
|
||||
.toUpperCase() + s.substring(1))
|
||||
.collect(Collectors.joining()));
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider<?>> byPreferredStore(DataStore store, DataSourceType type) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream().filter(d -> d.getFileProvider() != null)
|
||||
.filter(d -> d.prefersStore(store)).findAny();
|
||||
return ALL.stream()
|
||||
.filter(d -> type == null || d.getPrimaryType() == type)
|
||||
.filter(d -> d.getFileProvider() != null)
|
||||
.filter(d -> d.prefersStore(store, type))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public static Set<DataSourceProvider<?>> getAll() {
|
||||
|
|
|
@ -57,16 +57,17 @@ public class DataStoreProviders {
|
|||
|
||||
|
||||
public static <T extends DataStoreProvider> T byStore(DataStore store) {
|
||||
return (T) byStoreClass(store.getClass()).orElseThrow(() -> new IllegalArgumentException("Provider for " + store.getClass().getSimpleName() + " not found"));
|
||||
return (T) byStoreClass(store.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends DataStoreProvider> Optional<T> byStoreClass(Class<?> c) {
|
||||
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return (Optional<T>) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny();
|
||||
|
||||
return (T) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny().orElseThrow();
|
||||
}
|
||||
|
||||
public static Set<DataStoreProvider> getAll() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
@ -20,47 +22,69 @@ public class DialogHelper {
|
|||
public static Dialog addressQuery(Address address) {
|
||||
var hostNameQuery = Dialog.query("Hostname", false, true, false, address.getHostname(), QueryConverter.STRING);
|
||||
var portQuery = Dialog.query("Port", false, true, false, address.getPort(), QueryConverter.INTEGER);
|
||||
return Dialog.chain(hostNameQuery, portQuery).evaluateTo(() -> new Address(hostNameQuery.getResult(), portQuery.getResult()));
|
||||
return Dialog.chain(hostNameQuery, portQuery)
|
||||
.evaluateTo(() -> new Address(hostNameQuery.getResult(), portQuery.getResult()));
|
||||
}
|
||||
|
||||
public static Dialog machineQuery(DataStore store) {
|
||||
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("local");
|
||||
return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING).map((String name) -> {
|
||||
if (name.equals("local")) {
|
||||
return new LocalStore();
|
||||
}
|
||||
var storeName = XPipeDaemon.getInstance()
|
||||
.getStoreName(store)
|
||||
.orElse("local");
|
||||
return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING)
|
||||
.map((String name) -> {
|
||||
if (name.equals("local")) {
|
||||
return new LocalStore();
|
||||
}
|
||||
|
||||
var stored = XPipeDaemon.getInstance().getNamedStore(name);
|
||||
if (stored.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.format("Store not found: %s", name));
|
||||
}
|
||||
var stored = XPipeDaemon.getInstance()
|
||||
.getNamedStore(name);
|
||||
if (stored.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.format("Store not found: %s", name));
|
||||
}
|
||||
|
||||
if (!(stored.get() instanceof MachineFileStore)) {
|
||||
throw new IllegalArgumentException(String.format("Store not a machine store: %s", name));
|
||||
}
|
||||
if (!(stored.get() instanceof MachineFileStore)) {
|
||||
throw new IllegalArgumentException(String.format("Store not a machine store: %s", name));
|
||||
}
|
||||
|
||||
return stored.get();
|
||||
});
|
||||
return stored.get();
|
||||
});
|
||||
}
|
||||
|
||||
public static Dialog shellQuery(DataStore store) {
|
||||
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("local");
|
||||
return Dialog.query("Shell", false, true, false, storeName, QueryConverter.STRING).map((String name) -> {
|
||||
if (name.equals("local")) {
|
||||
return new LocalStore();
|
||||
}
|
||||
var storeName = XPipeDaemon.getInstance()
|
||||
.getStoreName(store)
|
||||
.orElse("local");
|
||||
return Dialog.query("Shell", false, true, false, storeName, QueryConverter.STRING)
|
||||
.map((String name) -> {
|
||||
if (name.equals("local")) {
|
||||
return new LocalStore();
|
||||
}
|
||||
|
||||
var stored = XPipeDaemon.getInstance().getNamedStore(name);
|
||||
if (stored.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.format("Store not found: %s", name));
|
||||
}
|
||||
var stored = XPipeDaemon.getInstance()
|
||||
.getNamedStore(name);
|
||||
if (stored.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.format("Store not found: %s", name));
|
||||
}
|
||||
|
||||
if (!(stored.get() instanceof ShellStore)) {
|
||||
throw new IllegalArgumentException(String.format("Store not a shell store: %s", name));
|
||||
}
|
||||
if (!(stored.get() instanceof ShellStore)) {
|
||||
throw new IllegalArgumentException(String.format("Store not a shell store: %s", name));
|
||||
}
|
||||
|
||||
return stored.get();
|
||||
});
|
||||
return stored.get();
|
||||
});
|
||||
}
|
||||
|
||||
public static Dialog charsetQuery(StreamCharset c, boolean all) {
|
||||
return Dialog.query("Charset", false, true, c != null &&!all, c, QueryConverter.CHARSET);
|
||||
}
|
||||
|
||||
public static Dialog newLineQuery(NewLine n, boolean all) {
|
||||
return Dialog.query("Newline", false, true, n != null &&!all, n, QueryConverter.NEW_LINE);
|
||||
}
|
||||
|
||||
|
||||
public static <T> Dialog query(String desc, T value, boolean required, QueryConverter<T> c, boolean all) {
|
||||
return Dialog.query(desc, false, required, value != null && !all, value, c);
|
||||
}
|
||||
|
||||
public static Dialog fileQuery(String name) {
|
||||
|
@ -71,6 +95,21 @@ public class DialogHelper {
|
|||
return Dialog.query("User", false, true, false, name, QueryConverter.STRING);
|
||||
}
|
||||
|
||||
public static Dialog namedStoreQuery(DataStore store, Class<? extends DataStore> filter) {
|
||||
var name = XPipeDaemon.getInstance()
|
||||
.getStoreName(store)
|
||||
.orElse(null);
|
||||
return Dialog.query("Store", false, true, false, name, QueryConverter.STRING)
|
||||
.map((String newName) -> {
|
||||
var found = XPipeDaemon.getInstance()
|
||||
.getNamedStore(newName)
|
||||
.orElseThrow();
|
||||
if (!filter.isAssignableFrom(found.getClass())) {
|
||||
throw new IllegalArgumentException("Incompatible store type");
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
|
||||
public static Dialog passwordQuery(Secret password) {
|
||||
return Dialog.querySecret("Password", false, true, password);
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.extension;
|
|||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.FilenameStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -19,11 +19,16 @@ public interface SimpleFileDataSourceProvider<T extends DataSource<?>> extends D
|
|||
|
||||
@Override
|
||||
default DataSource<?> convert(T in, DataSourceType t) throws Exception {
|
||||
return DataSourceProviders.byId("binary").createDefaultSource(in.getStore());
|
||||
return DataSourceProviders.byId("binary")
|
||||
.createDefaultSource(in.getStore());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean prefersStore(DataStore store) {
|
||||
default boolean prefersStore(DataStore store, DataSourceType type) {
|
||||
if (type != null && type != getPrimaryType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var e : getSupportedExtensions().entrySet()) {
|
||||
if (e.getValue() == null) {
|
||||
continue;
|
||||
|
@ -34,8 +39,9 @@ public interface SimpleFileDataSourceProvider<T extends DataSource<?>> extends D
|
|||
continue;
|
||||
}
|
||||
|
||||
if (store instanceof FileStore l) {
|
||||
return l.getFileName().endsWith("." + ext);
|
||||
if (store instanceof FilenameStore l) {
|
||||
return l.getFileExtension()
|
||||
.equalsIgnoreCase(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +56,7 @@ public interface SimpleFileDataSourceProvider<T extends DataSource<?>> extends D
|
|||
default String getNameI18nKey() {
|
||||
return i18nKey("displayName");
|
||||
}
|
||||
|
||||
Map<String, List<String>> getSupportedExtensions();
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,7 +15,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default T createDefaultSource(DataStore input) {
|
||||
default T createDefaultSource(DataStore input) throws Exception {
|
||||
try {
|
||||
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface XPipeDaemon {
|
||||
|
||||
|
@ -11,6 +17,16 @@ public interface XPipeDaemon {
|
|||
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
List<DataStore> getNamedStores();
|
||||
|
||||
public Image image(String file);
|
||||
|
||||
Comp<?> streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider);
|
||||
|
||||
Comp<?> namedStoreChooser(ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category);
|
||||
|
||||
Comp<?> sourceProviderChooser(Property<DataSourceProvider<?>> provider, DataSourceProvider.Category category);
|
||||
|
||||
Optional<DataStore> getNamedStore(String name);
|
||||
|
||||
Optional<String> getStoreName(DataStore store);
|
||||
|
|
|
@ -1,33 +1,39 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.comp.ReplacementComp;
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.fxcomps.SimpleComp;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
public class CharsetChoiceComp extends SimpleComp {
|
||||
|
||||
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
|
||||
private final Property<StreamCharset> charset;
|
||||
|
||||
private final Property<Charset> charset;
|
||||
|
||||
public CharsetChoiceComp(Property<Charset> charset) {
|
||||
public CharsetChoiceComp(Property<StreamCharset> charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
|
||||
var map = new LinkedHashMap<Charset, ObservableValue<String>>();
|
||||
for (var e : List.of(StandardCharsets.UTF_8, StandardCharsets.UTF_16,
|
||||
StandardCharsets.UTF_16BE, StandardCharsets.ISO_8859_1, Charset.forName("Windows-1251"), Charset.forName("Windows-1252"), StandardCharsets.US_ASCII)) {
|
||||
map.put(e, new SimpleStringProperty(e.displayName()));
|
||||
protected Region createSimple() {
|
||||
var builder = new CustomComboBoxBuilder<>(charset, streamCharset -> {
|
||||
return new Label(streamCharset.getCharset().displayName() + (streamCharset.hasByteOrderMark() ?
|
||||
" (BOM)" :
|
||||
""));
|
||||
}, new Label(I18n.get("extension.none")), null);
|
||||
builder.addHeader(I18n.get("extension.common"));
|
||||
for (var e : StreamCharset.COMMON) {
|
||||
builder.add(e);
|
||||
}
|
||||
return new ChoiceComp<>(charset, map);
|
||||
|
||||
builder.addHeader(I18n.get("extension.other"));
|
||||
builder.addFilter((charset, filter) -> {
|
||||
return charset.getCharset().displayName().contains(filter);
|
||||
});
|
||||
for (var e : StreamCharset.RARE) {
|
||||
builder.add(e);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class CustomComboBoxBuilder<T> {
|
||||
|
||||
private final Property<T> selected;
|
||||
private final Function<T, Node> nodeFunction;
|
||||
private final Map<Node, T> nodeMap = new HashMap<>();
|
||||
private final Map<Node, Runnable> actionsMap = new HashMap<>();
|
||||
private final List<Node> nodes = new ArrayList<>();
|
||||
private final Set<Node> disabledNodes = new HashSet<>();
|
||||
private final Node emptyNode;
|
||||
private final Predicate<T> veto;
|
||||
private BiPredicate<T, String> filterPredicate;
|
||||
private final Property<String> filterString = new SimpleStringProperty();
|
||||
private final List<T> filterable = new ArrayList<>();
|
||||
private Node filterNode;
|
||||
|
||||
public void addAction(Node node, Runnable run) {
|
||||
nodes.add(node);
|
||||
actionsMap.put(node, run);
|
||||
}
|
||||
|
||||
public Node add(T val) {
|
||||
var node = nodeFunction.apply(val);
|
||||
nodeMap.put(node, val);
|
||||
nodes.add(node);
|
||||
if (filterPredicate != null) {
|
||||
filterable.add(val);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public void addSeparator() {
|
||||
var sep = new Separator(Orientation.HORIZONTAL);
|
||||
nodes.add(sep);
|
||||
disabledNodes.add(sep);
|
||||
}
|
||||
|
||||
public void addHeader(String name) {
|
||||
var spacer = new Region();
|
||||
spacer.setPrefHeight(10);
|
||||
var header = new Label(name);
|
||||
header.setAlignment(Pos.CENTER);
|
||||
var v = new VBox(spacer, header, new Separator(Orientation.HORIZONTAL));
|
||||
v.setAlignment(Pos.CENTER);
|
||||
nodes.add(v);
|
||||
disabledNodes.add(v);
|
||||
}
|
||||
|
||||
public void addFilter(BiPredicate<T, String> filterPredicate) {
|
||||
this.filterPredicate = filterPredicate;
|
||||
|
||||
var spacer = new Region();
|
||||
spacer.setPrefHeight(10);
|
||||
var header = new FilterComp(filterString).createStructure();
|
||||
var v = new VBox(header.get());
|
||||
v.setAlignment(Pos.CENTER);
|
||||
nodes.add(v);
|
||||
filterNode = header.getText();
|
||||
}
|
||||
|
||||
public CustomComboBoxBuilder(Property<T> selected, Function<T, Node> nodeFunction, Node emptyNode, Predicate<T> veto) {
|
||||
this.selected = selected;
|
||||
this.nodeFunction = nodeFunction;
|
||||
this.emptyNode = emptyNode;
|
||||
this.veto = veto;
|
||||
}
|
||||
|
||||
public ComboBox<Node> build() {
|
||||
var cb = new ComboBox<Node>();
|
||||
cb.getItems().addAll(nodes);
|
||||
cb.setCellFactory((lv) -> {
|
||||
return new Cell();
|
||||
});
|
||||
cb.setButtonCell(new SelectedCell());
|
||||
SimpleChangeListener.apply(selected, c -> {
|
||||
var item = nodeMap.entrySet().stream().filter(e -> e.getValue() != null && e.getValue().equals(c)).map(e -> e.getKey()).findAny().orElse(null);
|
||||
cb.setValue(Optional.ofNullable(item).orElse(emptyNode));
|
||||
});
|
||||
cb.valueProperty().addListener((c, o, n) -> {
|
||||
if (nodeMap.containsKey(n)) {
|
||||
if (veto != null && !veto.test(nodeMap.get(n))) {
|
||||
cb.setValue(o);
|
||||
;
|
||||
return;
|
||||
}
|
||||
selected.setValue(nodeMap.get(n));
|
||||
}
|
||||
|
||||
if (actionsMap.containsKey(n)) {
|
||||
cb.setValue(o);
|
||||
actionsMap.get(n).run();
|
||||
}
|
||||
});
|
||||
|
||||
if (filterPredicate != null) {
|
||||
|
||||
SimpleChangeListener.apply(filterString, c -> {
|
||||
var filteredNodes = nodes.stream().filter(e -> e.equals(cb.getValue()) || !(nodeMap.get(e) != null && (
|
||||
filterable.contains(nodeMap.get(e)) && filterString.getValue() != null && !filterPredicate.test(nodeMap.get(e), c)))).toList();
|
||||
cb.setItems(FXCollections.observableList(filteredNodes));
|
||||
});
|
||||
|
||||
filterNode.sceneProperty().addListener((c, o, n) -> {
|
||||
if (n != null) {
|
||||
n.getWindow().focusedProperty().addListener((c2, o2, n2) -> {
|
||||
Platform.runLater(() -> {
|
||||
filterNode.requestFocus();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
filterNode.requestFocus();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
private class SelectedCell extends ListCell<Node> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Node item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.equals(emptyNode)) {
|
||||
setGraphic(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nodeMap.containsKey(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = nodeMap.get(item);
|
||||
var newNode = nodeFunction.apply(val);
|
||||
setGraphic(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
private class Cell extends ListCell<Node> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Node item, boolean empty) {
|
||||
setGraphic(item);
|
||||
if (getItem() == item) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.updateItem(item, empty);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGraphic(item);
|
||||
if (disabledNodes.contains(item)) {
|
||||
this.setDisable(true);
|
||||
// this.setPadding(Insets.EMPTY);
|
||||
}else {
|
||||
this.setDisable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.charsetter.StreamCharset;
|
||||
import io.xpipe.core.util.Secret;
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.extension.Validator;
|
||||
|
@ -12,7 +13,6 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.layout.Region;
|
||||
import net.synedra.validatorfx.Check;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
@ -43,6 +43,11 @@ public class DynamicOptionsBuilder<T> {
|
|||
this.title = title;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addTitle(ObservableValue<String> title) {
|
||||
entries.add(new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> makeLazy() {
|
||||
var p = props.get(props.size() - 1);
|
||||
props.remove(p);
|
||||
|
@ -108,7 +113,7 @@ public class DynamicOptionsBuilder<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder<T> addCharset(Property<Charset> prop) {
|
||||
public DynamicOptionsBuilder<T> addCharset(Property<StreamCharset> prop) {
|
||||
var comp = new CharsetChoiceComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.charset"), comp));
|
||||
props.add(prop);
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.fxcomps.SimpleCompStructure;
|
|||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -33,6 +34,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
|||
flow.setAlignment(Pos.CENTER);
|
||||
flow.setHgap(7);
|
||||
flow.setVgap(7);
|
||||
flow.setPadding(new Insets(8, 0, 0, 0));
|
||||
|
||||
var nameRegions = new ArrayList<Region>();
|
||||
var compRegions = new ArrayList<Region>();
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.fxcomps.Comp;
|
||||
import io.xpipe.fxcomps.CompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
public class FilterComp extends Comp<FilterComp.Structure> {
|
||||
|
||||
public FilterComp(Property<String> filterText) {
|
||||
this.filterText = filterText;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class Structure implements CompStructure<StackPane> {
|
||||
StackPane pane;
|
||||
Node inactiveIcon;
|
||||
Label inactiveText;
|
||||
TextField text;
|
||||
|
||||
@Override
|
||||
public StackPane get() {
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
|
||||
private final Property<String> filterText;
|
||||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
var fi = new FontIcon("mdi2m-magnify");
|
||||
var bgLabel = new Label("Search ...", fi);
|
||||
bgLabel.getStyleClass().add("background");
|
||||
var filter = new TextField();
|
||||
PlatformThread.connect(filterText, filter.textProperty());
|
||||
bgLabel.visibleProperty().bind(Bindings.createBooleanBinding(() -> (filter.getText() == null || filter.getText().isEmpty()),
|
||||
filter.textProperty(), filter.focusedProperty()));
|
||||
|
||||
var stack = new StackPane(bgLabel, filter);
|
||||
stack.getStyleClass().add("filter-comp");
|
||||
bgLabel.prefHeightProperty().bind(stack.heightProperty());
|
||||
filter.prefHeightProperty().bind(stack.heightProperty());
|
||||
|
||||
return Structure.builder().inactiveIcon(fi).inactiveText(bgLabel).text(filter).pane(stack).build();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package io.xpipe.extension.event;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
|
@ -19,12 +17,13 @@ public abstract class EventHandler {
|
|||
if (cat == null) {
|
||||
cat = "log";
|
||||
}
|
||||
LoggerFactory.getLogger(cat).info(te.getMessage());
|
||||
System.out.println("[" + cat + "] " + te.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ErrorEvent ee) {
|
||||
LoggerFactory.getLogger(EventHandler.class).error(ee.getDescription(), ee.getThrowable());
|
||||
if (ee.getDescription() != null) System.err.println(ee.getDescription());
|
||||
if (ee.getThrowable() != null) ee.getThrowable().printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package io.xpipe.extension.prefs;
|
||||
|
||||
import com.dlsc.preferencesfx.model.Setting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PrefsHandler {
|
||||
|
||||
void addSetting(List<String> category, String group, Setting<?,?> setting);
|
||||
// void addSetting(List<String> category, String group, Setting<?,?> setting);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package io.xpipe.api.test;
|
||||
package io.xpipe.extension.test;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
public class DaemonControl {
|
||||
public class ExtensionTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
ConnectionFactory.start();
|
||||
ExtensionTestConnector.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void teardown() throws Exception {
|
||||
ConnectionFactory.stop();
|
||||
ExtensionTestConnector.stop();
|
||||
}
|
||||
}
|
|
@ -1,24 +1,36 @@
|
|||
package io.xpipe.api.test;
|
||||
package io.xpipe.extension.test;
|
||||
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
import io.xpipe.beacon.BeaconClient;
|
||||
import io.xpipe.beacon.BeaconServer;
|
||||
import io.xpipe.core.charsetter.Charsetter;
|
||||
import io.xpipe.core.charsetter.CharsetterContext;
|
||||
import io.xpipe.core.util.JacksonHelper;
|
||||
import io.xpipe.extension.DataSourceProviders;
|
||||
|
||||
public class ConnectionFactory {
|
||||
public class ExtensionTestConnector {
|
||||
|
||||
private static boolean alreadyStarted;
|
||||
|
||||
public static void start() throws Exception {
|
||||
DataSourceProviders.init(ModuleLayer.boot());
|
||||
JacksonHelper.initClassBased();
|
||||
Charsetter.init(CharsetterContext.empty());
|
||||
|
||||
if (BeaconServer.isRunning()) {
|
||||
alreadyStarted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BeaconServer.tryStart()) {
|
||||
throw new AssertionError();
|
||||
Process process = null;
|
||||
if ((process = BeaconServer.tryStartCustom()) != null) {
|
||||
} else {
|
||||
if ((process = BeaconServer.tryStart()) == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
XPipeConnection.waitForStartup().orElseThrow();
|
||||
XPipeConnection.waitForStartup(process).orElseThrow();
|
||||
if (!BeaconServer.isRunning()) {
|
||||
throw new AssertionError();
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceConnection;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.source.TableWriteConnection;
|
||||
|
||||
public class AppendingTableWriteConnection extends AppendingWriteConnection implements TableWriteConnection {
|
||||
|
||||
public AppendingTableWriteConnection(DataSource<?> source, DataSourceConnection connection
|
||||
) {
|
||||
super(DataSourceType.TABLE, source, connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor() {
|
||||
return ((TableWriteConnection)connection).writeLinesAcceptor();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceConnection;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.source.TextWriteConnection;
|
||||
|
||||
public class AppendingTextWriteConnection extends AppendingWriteConnection implements TextWriteConnection {
|
||||
|
||||
public AppendingTextWriteConnection(
|
||||
DataSource<?> source, DataSourceConnection connection
|
||||
) {
|
||||
super(DataSourceType.TEXT, source, connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLine(String line) throws Exception {
|
||||
((TextWriteConnection) connection).writeLine(line);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceConnection;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.extension.DataSourceProviders;
|
||||
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class AppendingWriteConnection implements DataSourceConnection {
|
||||
|
||||
private final DataSourceType type;
|
||||
private final DataSource<?> source;
|
||||
protected final DataSourceConnection connection;
|
||||
|
||||
public AppendingWriteConnection(DataSourceType type, DataSource<?> source, DataSourceConnection connection) {
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
var temp = Files.createTempFile(null, null);
|
||||
var nativeStore = FileStore.local(temp);
|
||||
var nativeSource = DataSourceProviders.getNativeDataSourceDescriptorForType(type).createDefaultSource(nativeStore);
|
||||
if (source.getStore().canOpen()) {
|
||||
try (var in = source.openReadConnection(); var out = nativeSource.openWriteConnection()) {
|
||||
in.forward(out);
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
connection.init();
|
||||
if (source.getStore().canOpen()) {
|
||||
|
||||
try (var in = nativeSource.openReadConnection()) {
|
||||
in.forward(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.util.JacksonHelper;
|
||||
import io.xpipe.extension.DataSourceProviders;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExtensionJacksonModule extends SimpleModule {
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
addSerializer(DataSource.class, new DataSourceSerializer());
|
||||
addDeserializer(DataSource.class, new DataSourceDeserializer());
|
||||
|
||||
context.addSerializers(_serializers);
|
||||
context.addDeserializers(_deserializers);
|
||||
}
|
||||
|
||||
public static class DataSourceSerializer extends JsonSerializer<DataSource> {
|
||||
|
||||
@Override
|
||||
public void serialize(DataSource value, JsonGenerator jgen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
|
||||
ObjectMapper mapper = JacksonHelper.newMapper(ExtensionJacksonModule.class);
|
||||
var prov = DataSourceProviders.byDataSourceClass(value.getClass());
|
||||
ObjectNode objectNode = mapper.valueToTree(value);
|
||||
objectNode.put("type", prov.getId());
|
||||
jgen.writeTree(objectNode);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DataSourceDeserializer extends JsonDeserializer<DataSource> {
|
||||
|
||||
@Override
|
||||
public DataSource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
var mapper = JacksonHelper.newMapper(ExtensionJacksonModule.class);
|
||||
var tree = (ObjectNode) mapper.readTree(p);
|
||||
var type = tree.get("type").textValue();
|
||||
var prov = DataSourceProviders.byId(type);
|
||||
return mapper.treeToValue(tree, prov.getSourceClass());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,35 @@
|
|||
import com.fasterxml.jackson.databind.Module;
|
||||
import io.xpipe.extension.DataSourceProvider;
|
||||
import io.xpipe.extension.SupportedApplicationProvider;
|
||||
import io.xpipe.extension.util.ExtensionJacksonModule;
|
||||
|
||||
module io.xpipe.extension {
|
||||
open module io.xpipe.extension {
|
||||
exports io.xpipe.extension;
|
||||
exports io.xpipe.extension.comp;
|
||||
exports io.xpipe.extension.event;
|
||||
exports io.xpipe.extension.prefs;
|
||||
exports io.xpipe.extension.util;
|
||||
exports io.xpipe.extension.test;
|
||||
|
||||
requires transitive io.xpipe.core;
|
||||
requires transitive javafx.base;
|
||||
requires javafx.graphics;
|
||||
requires transitive javafx.controls;
|
||||
requires io.xpipe.fxcomps;
|
||||
requires static lombok;
|
||||
requires static com.dlsc.preferencesfx;
|
||||
requires static com.dlsc.formsfx;
|
||||
requires static org.slf4j;
|
||||
requires static org.controlsfx.controls;
|
||||
requires java.desktop;
|
||||
requires org.fxmisc.richtext;
|
||||
requires static net.synedra.validatorfx;
|
||||
requires org.fxmisc.flowless;
|
||||
requires org.fxmisc.undofx;
|
||||
requires org.fxmisc.wellbehavedfx;
|
||||
requires org.reactfx;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires io.xpipe.beacon;
|
||||
requires io.xpipe.api;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires static org.junit.jupiter.api;
|
||||
requires transitive javafx.base;
|
||||
requires static javafx.graphics;
|
||||
requires static javafx.controls;
|
||||
requires static io.xpipe.fxcomps;
|
||||
requires static lombok;
|
||||
requires static org.controlsfx.controls;
|
||||
requires static java.desktop;
|
||||
requires static org.fxmisc.richtext;
|
||||
requires static net.synedra.validatorfx;
|
||||
requires static org.fxmisc.flowless;
|
||||
requires static org.fxmisc.undofx;
|
||||
requires static org.fxmisc.wellbehavedfx;
|
||||
requires static org.reactfx;
|
||||
requires static org.kordamp.ikonli.javafx;
|
||||
requires static com.jfoenix;
|
||||
|
||||
uses DataSourceProvider;
|
||||
|
@ -37,4 +39,6 @@ module io.xpipe.extension {
|
|||
uses io.xpipe.extension.prefs.PrefsProvider;
|
||||
uses io.xpipe.extension.DataStoreProvider;
|
||||
uses io.xpipe.extension.XPipeDaemon;
|
||||
|
||||
provides Module with ExtensionJacksonModule;
|
||||
}
|
|
@ -49,28 +49,28 @@ jreleaser {
|
|||
api {
|
||||
artifact {
|
||||
distributionType = 'SINGLE_JAR'
|
||||
path = 'api/build/libs/api-{{version}}.jar'
|
||||
path = 'api/build/libs/xpipe-api-{{version}}.jar'
|
||||
}
|
||||
}
|
||||
|
||||
core {
|
||||
artifact {
|
||||
distributionType = 'SINGLE_JAR'
|
||||
path = 'core/build/libs/core-{{version}}.jar'
|
||||
path = 'core/build/libs/xpipe-core-{{version}}.jar'
|
||||
}
|
||||
}
|
||||
|
||||
beacon {
|
||||
artifact {
|
||||
distributionType = 'SINGLE_JAR'
|
||||
path = 'beacon/build/libs/beacon-{{version}}.jar'
|
||||
path = 'beacon/build/libs/xpipe-beacon-{{version}}.jar'
|
||||
}
|
||||
}
|
||||
|
||||
extension {
|
||||
artifact {
|
||||
distributionType = 'SINGLE_JAR'
|
||||
path = 'extension/build/libs/extension-{{version}}.jar'
|
||||
path = 'extension/build/libs/xpipe-extension-{{version}}.jar'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.0.1.4-SNAPSHOT
|
||||
0.0.1.6-SNAPSHOT
|
Loading…
Reference in a new issue