mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Rework
This commit is contained in:
parent
0f37600c96
commit
b1a75f6d21
56 changed files with 673 additions and 285 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,4 +3,6 @@ build/
|
||||||
.idea
|
.idea
|
||||||
dev.properties
|
dev.properties
|
||||||
extensions.txt
|
extensions.txt
|
||||||
local/
|
local/
|
||||||
|
.vscode
|
||||||
|
bin
|
|
@ -7,6 +7,7 @@ plugins {
|
||||||
|
|
||||||
apply from: "$rootDir/deps/java.gradle"
|
apply from: "$rootDir/deps/java.gradle"
|
||||||
apply from: "$rootDir/deps/junit.gradle"
|
apply from: "$rootDir/deps/junit.gradle"
|
||||||
|
apply from: "$rootDir/deps/jackson.gradle"
|
||||||
apply from: 'publish.gradle'
|
apply from: 'publish.gradle'
|
||||||
apply from: "$rootDir/deps/publish-base.gradle"
|
apply from: "$rootDir/deps/publish-base.gradle"
|
||||||
|
|
||||||
|
@ -19,18 +20,18 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
enabled = false
|
enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Fix warnings about missing annotations
|
|
||||||
compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
|
||||||
testCompileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
|
||||||
|
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
implementation project(':beacon')
|
implementation project(':beacon')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
testImplementation.extendsFrom(dep)
|
||||||
|
}
|
||||||
|
|
||||||
def canTestWithDev = findProject(':app') != null
|
def canTestWithDev = findProject(':app') != null
|
||||||
def home = System.getenv('XPIPE_HOME')
|
def home = System.getenv('XPIPE_HOME')
|
||||||
String daemonCommand = canTestWithDev ?
|
String daemonCommand = canTestWithDev ?
|
||||||
|
@ -52,7 +53,7 @@ test {
|
||||||
" -Dio.xpipe.app.logLevel=trace"
|
" -Dio.xpipe.app.logLevel=trace"
|
||||||
|
|
||||||
// API properties
|
// API properties
|
||||||
// systemProperty 'io.xpipe.beacon.debugOutput', "true"
|
systemProperty 'io.xpipe.beacon.debugOutput', "true"
|
||||||
systemProperty 'io.xpipe.beacon.debugExecOutput', "true"
|
systemProperty 'io.xpipe.beacon.debugExecOutput', "true"
|
||||||
systemProperty "io.xpipe.beacon.port", "21722"
|
systemProperty "io.xpipe.beacon.port", "21722"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package io.xpipe.api;
|
package io.xpipe.api;
|
||||||
|
|
||||||
import io.xpipe.api.impl.DataSourceImpl;
|
import io.xpipe.api.impl.DataSourceImpl;
|
||||||
import io.xpipe.core.source.*;
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.core.source.DataSourceReference;
|
||||||
|
import io.xpipe.core.source.DataSourceType;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -9,7 +11,6 @@ import java.io.UncheckedIOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a reference to a data source that is managed by X-Pipe.
|
* Represents a reference to a data source that is managed by X-Pipe.
|
||||||
|
@ -85,36 +86,36 @@ public interface DataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source.
|
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||||
*/
|
*/
|
||||||
public static DataSource createAnonymous(String type, Map<String,String> config, Path path) {
|
public static DataSource createAnonymous(String type, Path path) {
|
||||||
return create(null, type, config, path);
|
return create(null, type, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)}.
|
* Wrapper for {@link #create(DataSourceId, String, InputStream)}.
|
||||||
*/
|
*/
|
||||||
public static DataSource create(DataSourceId id, String type, Map<String,String> config, Path path) {
|
public static DataSource create(DataSourceId id, String type, Path path) {
|
||||||
try (var in = Files.newInputStream(path)) {
|
try (var in = Files.newInputStream(path)) {
|
||||||
return create(id, type, config, in);
|
return create(id, type, in);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source.
|
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||||
*/
|
*/
|
||||||
public static DataSource createAnonymous(String type, Map<String,String> config, URL url) {
|
public static DataSource createAnonymous(String type, URL url) {
|
||||||
return create(null, type, config, url);
|
return create(null, type, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)}.
|
* Wrapper for {@link #create(DataSourceId, String, InputStream)}.
|
||||||
*/
|
*/
|
||||||
public static DataSource create(DataSourceId id, String type, Map<String,String> config, URL url) {
|
public static DataSource create(DataSourceId id, String type, URL url) {
|
||||||
try (var in = url.openStream()) {
|
try (var in = url.openStream()) {
|
||||||
return create(id, type, config, in);
|
return create(id, type, in);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
|
@ -122,10 +123,10 @@ public interface DataSource {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source.
|
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||||
*/
|
*/
|
||||||
public static DataSource createAnonymous(String type, Map<String,String> config, InputStream in) {
|
public static DataSource createAnonymous(String type, InputStream in) {
|
||||||
return create(null, type, config, in);
|
return create(null, type, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,12 +134,11 @@ public interface DataSource {
|
||||||
*
|
*
|
||||||
* @param id the data source id
|
* @param id the data source id
|
||||||
* @param type the data source type
|
* @param type the data source type
|
||||||
* @param config additional configuration options for the specific data source type
|
|
||||||
* @param in the input stream to read
|
* @param in the input stream to read
|
||||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||||
*/
|
*/
|
||||||
public static DataSource create(DataSourceId id, String type, Map<String,String> config, InputStream in) {
|
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||||
return DataSourceImpl.create(id, type, config, in);
|
return DataSourceImpl.create(id, type, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package io.xpipe.api.connector;
|
package io.xpipe.api.connector;
|
||||||
|
|
||||||
import io.xpipe.beacon.*;
|
import io.xpipe.beacon.*;
|
||||||
|
import io.xpipe.beacon.exchange.cli.DialogExchange;
|
||||||
|
import io.xpipe.core.dialog.DialogReference;
|
||||||
import io.xpipe.core.util.JacksonHelper;
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -13,6 +15,22 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void finishDialog(DialogReference reference) {
|
||||||
|
try (var con = new XPipeConnection()) {
|
||||||
|
con.constructSocket();
|
||||||
|
while (true) {
|
||||||
|
DialogExchange.Response response = con.performSimpleExchange(DialogExchange.Request.builder().dialogKey(reference.getDialogId()).build());
|
||||||
|
if (response.getElement() == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (BeaconException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BeaconException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void execute(Handler handler) {
|
public static void execute(Handler handler) {
|
||||||
try (var con = new XPipeConnection()) {
|
try (var con = new XPipeConnection()) {
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
|
@ -74,7 +92,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<BeaconClient> waitForStartup() {
|
public static Optional<BeaconClient> waitForStartup() {
|
||||||
for (int i = 0; i < 40; i++) {
|
for (int i = 0; i < 80; i++) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import io.xpipe.core.source.DataSourceId;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
import io.xpipe.core.source.DataSourceReference;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public abstract class DataSourceImpl implements DataSource {
|
public abstract class DataSourceImpl implements DataSource {
|
||||||
|
|
||||||
|
@ -19,29 +18,30 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
||||||
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
||||||
var config = new DataSourceConfig(res.getProvider(), res.getConfig());
|
var config = new DataSourceConfig(res.getProvider(), res.getConfig());
|
||||||
switch (res.getInfo().getType()) {
|
return switch (res.getInfo().getType()) {
|
||||||
case TABLE -> {
|
case TABLE -> {
|
||||||
var data = res.getInfo().asTable();
|
var data = res.getInfo().asTable();
|
||||||
return new DataTableImpl(res.getId(), config, data);
|
yield new DataTableImpl(res.getId(), config, data);
|
||||||
}
|
}
|
||||||
case STRUCTURE -> {
|
case STRUCTURE -> {
|
||||||
var info = res.getInfo().asStructure();
|
var info = res.getInfo().asStructure();
|
||||||
return new DataStructureImpl(res.getId(), config, info);
|
yield new DataStructureImpl(res.getId(), config, info);
|
||||||
}
|
}
|
||||||
case TEXT -> {
|
case TEXT -> {
|
||||||
var info = res.getInfo().asText();
|
var info = res.getInfo().asText();
|
||||||
return new DataTextImpl(res.getId(), config, info);
|
yield new DataTextImpl(res.getId(), config, info);
|
||||||
}
|
}
|
||||||
case RAW -> {
|
case RAW -> {
|
||||||
var info = res.getInfo().asRaw();
|
var info = res.getInfo().asRaw();
|
||||||
return new DataRawImpl(res.getId(), config, info);
|
yield new DataRawImpl(res.getId(), config, info);
|
||||||
}
|
}
|
||||||
}
|
case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getInfo().getType());
|
||||||
throw new AssertionError();
|
default -> throw new IllegalArgumentException("Unexpected value: " + res.getInfo().getType());
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource create(DataSourceId id, String type, Map<String,String> config, InputStream in) {
|
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||||
var res = XPipeConnection.execute(con -> {
|
var res = XPipeConnection.execute(con -> {
|
||||||
var req = StoreStreamExchange.Request.builder().build();
|
var req = StoreStreamExchange.Request.builder().build();
|
||||||
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out));
|
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out));
|
||||||
|
@ -53,6 +53,8 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
var startReq = ReadExchange.Request.builder()
|
var startReq = ReadExchange.Request.builder()
|
||||||
.provider(type)
|
.provider(type)
|
||||||
.store(store)
|
.store(store)
|
||||||
|
.target(id)
|
||||||
|
.configureAll(false)
|
||||||
.build();
|
.build();
|
||||||
var startRes = XPipeConnection.execute(con -> {
|
var startRes = XPipeConnection.execute(con -> {
|
||||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||||
|
@ -60,13 +62,8 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
});
|
});
|
||||||
|
|
||||||
var configInstance = startRes.getConfig();
|
var configInstance = startRes.getConfig();
|
||||||
//TODO
|
XPipeConnection.finishDialog(configInstance);
|
||||||
// configInstance.getConfigInstance().getCurrentValues().putAll(config);
|
|
||||||
// var endReq = ReadExecuteExchange.Request.builder()
|
|
||||||
// .target(id).dataStore(store).config(configInstance).build();
|
|
||||||
// XPipeConnection.execute(con -> {
|
|
||||||
// con.performSimpleExchange(endReq);
|
|
||||||
// });
|
|
||||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||||
return get(ref);
|
return get(ref);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import io.xpipe.api.DataTable;
|
||||||
import io.xpipe.api.DataTableAccumulator;
|
import io.xpipe.api.DataTableAccumulator;
|
||||||
import io.xpipe.api.connector.XPipeConnection;
|
import io.xpipe.api.connector.XPipeConnection;
|
||||||
import io.xpipe.api.util.TypeDescriptor;
|
import io.xpipe.api.util.TypeDescriptor;
|
||||||
|
import io.xpipe.beacon.BeaconException;
|
||||||
|
import io.xpipe.beacon.exchange.ReadExchange;
|
||||||
import io.xpipe.beacon.exchange.StoreStreamExchange;
|
import io.xpipe.beacon.exchange.StoreStreamExchange;
|
||||||
import io.xpipe.core.data.node.DataStructureNode;
|
import io.xpipe.core.data.node.DataStructureNode;
|
||||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||||
|
@ -14,6 +16,7 @@ import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||||
import io.xpipe.core.source.DataSourceId;
|
import io.xpipe.core.source.DataSourceId;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
import io.xpipe.core.source.DataSourceReference;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@ -23,25 +26,35 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
||||||
private final TupleType type;
|
private final TupleType type;
|
||||||
private int rows;
|
private int rows;
|
||||||
private TupleType writtenDescriptor;
|
private TupleType writtenDescriptor;
|
||||||
|
private OutputStream bodyOutput;
|
||||||
|
|
||||||
public DataTableAccumulatorImpl(TupleType type) {
|
public DataTableAccumulatorImpl(TupleType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
connection = XPipeConnection.open();
|
connection = XPipeConnection.open();
|
||||||
connection.sendRequest(StoreStreamExchange.Request.builder().build());
|
connection.sendRequest(StoreStreamExchange.Request.builder().build());
|
||||||
connection.sendBody();
|
bodyOutput = connection.sendBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized DataTable finish(DataSourceId id) {
|
public synchronized DataTable finish(DataSourceId id) {
|
||||||
connection.withOutputStream(OutputStream::close);
|
try {
|
||||||
|
bodyOutput.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BeaconException(e);
|
||||||
|
}
|
||||||
|
|
||||||
StoreStreamExchange.Response res = connection.receiveResponse();
|
StoreStreamExchange.Response res = connection.receiveResponse();
|
||||||
connection.close();
|
connection.close();
|
||||||
|
|
||||||
// var req = ReadExecuteExchange.Request.builder()
|
var req = ReadExchange.Request.builder()
|
||||||
// .target(id).dataStore(res.getStore()).build();
|
.target(id).store(res.getStore()).provider("xpbt").configureAll(false).build();
|
||||||
// XPipeConnection.execute(con -> {
|
ReadExchange.Response response = XPipeConnection.execute(con -> {
|
||||||
// con.performSimpleExchange(req);
|
return con.performSimpleExchange(req);
|
||||||
// });
|
});
|
||||||
|
|
||||||
|
var configInstance = response.getConfig();
|
||||||
|
XPipeConnection.finishDialog(configInstance);
|
||||||
|
|
||||||
return DataSource.get(DataSourceReference.id(id)).asTable();
|
return DataSource.get(DataSourceReference.id(id)).asTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,11 @@ import io.xpipe.core.source.DataSourceId;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DataTableTest extends DaemonControl {
|
public class DataTableTest extends DaemonControl {
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setupStorage() throws Exception {
|
public static void setupStorage() throws Exception {
|
||||||
DataSource.create(DataSourceId.fromString(":usernames"), "csv", Map.of(), DataTableTest.class.getResource("username.csv"));
|
DataSource.create(DataSourceId.fromString(":usernames"), "csv", DataTableTest.class.getResource("username.csv"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -15,8 +15,8 @@ module io.xpipe.beacon {
|
||||||
opens io.xpipe.beacon.exchange.data;
|
opens io.xpipe.beacon.exchange.data;
|
||||||
opens io.xpipe.beacon.exchange.cli;
|
opens io.xpipe.beacon.exchange.cli;
|
||||||
|
|
||||||
requires com.fasterxml.jackson.core;
|
requires static com.fasterxml.jackson.core;
|
||||||
requires com.fasterxml.jackson.databind;
|
requires static com.fasterxml.jackson.databind;
|
||||||
requires transitive io.xpipe.core;
|
requires transitive io.xpipe.core;
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,8 @@ plugins {
|
||||||
id 'org.jreleaser' version '1.0.0'
|
id 'org.jreleaser' version '1.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project == rootProject) {
|
if(project == rootProject) {
|
||||||
plugins {
|
apply plugin: "io.codearte.nexus-staging"
|
||||||
id "io.codearte.nexus-staging" version "0.30.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version file('misc/version').text
|
version file('misc/version').text
|
||||||
|
|
|
@ -24,3 +24,7 @@ archivesBaseName = 'core'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies{
|
||||||
|
compileOnly 'org.apache.commons:commons-exec:1.3'
|
||||||
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNull() {
|
public boolean isNull() {
|
||||||
throw unsupported("null check");
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final ValueNode asValue() {
|
public final ValueNode asValue() {
|
||||||
|
|
|
@ -106,4 +106,10 @@ public abstract class ValueNode extends DataStructureNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract byte[] getRawData();
|
public abstract byte[] getRawData();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull() {
|
||||||
|
return getRawData() == null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Dialog is a sequence of questions and answers.
|
* A Dialog is a sequence of questions and answers.
|
||||||
*
|
* <p>
|
||||||
* The dialogue API is only used for the command line interface.
|
* The dialogue API is only used for the command line interface.
|
||||||
* Therefore, the actual implementation is handled by the command line component.
|
* Therefore, the actual implementation is handled by the command line component.
|
||||||
* This API provides a way of creating server-side dialogues which makes
|
* This API provides a way of creating server-side dialogues which makes
|
||||||
* it possible to create extensions that provide a commandline configuration component.
|
* it possible to create extensions that provide a commandline configuration component.
|
||||||
*
|
* <p>
|
||||||
* When a Dialog is completed, it can also be optionally evaluated to a value, which can be queried by calling {@link #getResult()}.
|
* When a Dialog is completed, it can also be optionally evaluated to a value, which can be queried by calling {@link #getResult()}.
|
||||||
* The evaluation function can be set with {@link #evaluateTo(Supplier)}.
|
* The evaluation function can be set with {@link #evaluateTo(Supplier)}.
|
||||||
* Alternatively, a dialogue can also copy the evaluation function of another dialogue with {@link #evaluateTo(Dialog)}.
|
* Alternatively, a dialogue can also copy the evaluation function of another dialogue with {@link #evaluateTo(Dialog)}.
|
||||||
|
@ -74,9 +74,9 @@ public abstract class Dialog {
|
||||||
* Creates a choice dialogue.
|
* Creates a choice dialogue.
|
||||||
*
|
*
|
||||||
* @param description the shown question description
|
* @param description the shown question description
|
||||||
* @param elements the available elements to choose from
|
* @param elements the available elements to choose from
|
||||||
* @param required signals whether a choice is required or can be left empty
|
* @param required signals whether a choice is required or can be left empty
|
||||||
* @param selected the selected element index
|
* @param selected the selected element index
|
||||||
*/
|
*/
|
||||||
public static Dialog.Choice choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
public static Dialog.Choice choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
||||||
Dialog.Choice c = new Dialog.Choice(description, elements, required, selected);
|
Dialog.Choice c = new Dialog.Choice(description, elements, required, selected);
|
||||||
|
@ -88,10 +88,10 @@ public abstract class Dialog {
|
||||||
* Creates a choice dialogue from a set of objects.
|
* Creates a choice dialogue from a set of objects.
|
||||||
*
|
*
|
||||||
* @param description the shown question description
|
* @param description the shown question description
|
||||||
* @param toString a function that maps the objects to a string
|
* @param toString a function that maps the objects to a string
|
||||||
* @param required signals whether choices required or can be left empty
|
* @param required signals whether choices required or can be left empty
|
||||||
* @param def the element which is selected by default
|
* @param def the element which is selected by default
|
||||||
* @param vals the range of possible elements
|
* @param vals the range of possible elements
|
||||||
*/
|
*/
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public static <T> Dialog.Choice choice(String description, Function<T, String> toString, boolean required, T def, T... vals) {
|
public static <T> Dialog.Choice choice(String description, Function<T, String> toString, boolean required, T def, T... vals) {
|
||||||
|
@ -133,7 +133,7 @@ public abstract class Dialog {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T getConvertedValue() {
|
private <T> T getConvertedValue() {
|
||||||
return element.getConvertedValue();
|
return element.getConvertedValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,13 +142,13 @@ public abstract class Dialog {
|
||||||
* Creates a simple query dialogue.
|
* Creates a simple query dialogue.
|
||||||
*
|
*
|
||||||
* @param description the shown question description
|
* @param description the shown question description
|
||||||
* @param newLine signals whether the query should be done on a new line or not
|
* @param newLine signals whether the query should be done on a new line or not
|
||||||
* @param required signals whether the query can be left empty or not
|
* @param required signals whether the query can be left empty or not
|
||||||
* @param quiet signals whether the user should be explicitly queried for the value.
|
* @param quiet signals whether the user should be explicitly queried for the value.
|
||||||
* In case the user is not queried, a value can still be set using the command line arguments
|
* In case the user is not queried, a value can still be set using the command line arguments
|
||||||
* that allow to set the specific value for a configuration query parameter
|
* that allow to set the specific value for a configuration query parameter
|
||||||
* @param value the default value
|
* @param value the default value
|
||||||
* @param converter the converter
|
* @param converter the converter
|
||||||
*/
|
*/
|
||||||
public static <T> Dialog.Query query(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter) {
|
public static <T> Dialog.Query query(String description, boolean newLine, boolean required, boolean quiet, T value, QueryConverter<T> converter) {
|
||||||
var q = new <T>Dialog.Query(description, newLine, required, quiet, value, converter, false);
|
var q = new <T>Dialog.Query(description, newLine, required, quiet, value, converter, false);
|
||||||
|
@ -197,7 +197,8 @@ public abstract class Dialog {
|
||||||
if (currentElement == null) {
|
if (currentElement == null) {
|
||||||
DialogElement next = null;
|
DialogElement next = null;
|
||||||
while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
|
while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
|
||||||
};
|
}
|
||||||
|
;
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,10 +379,10 @@ public abstract class Dialog {
|
||||||
* Creates a dialogue that will fork the control flow.
|
* Creates a dialogue that will fork the control flow.
|
||||||
*
|
*
|
||||||
* @param description the shown question description
|
* @param description the shown question description
|
||||||
* @param elements the available elements to choose from
|
* @param elements the available elements to choose from
|
||||||
* @param required signals whether a choice is required or not
|
* @param required signals whether a choice is required or not
|
||||||
* @param selected the index of the element that is selected by default
|
* @param selected the index of the element that is selected by default
|
||||||
* @param c the dialogue index mapping function
|
* @param c the dialogue index mapping function
|
||||||
*/
|
*/
|
||||||
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected, Function<Integer, Dialog> c) {
|
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected, Function<Integer, Dialog> c) {
|
||||||
var choice = new ChoiceElement(description, elements, required, selected);
|
var choice = new ChoiceElement(description, elements, required, selected);
|
||||||
|
@ -424,7 +425,7 @@ public abstract class Dialog {
|
||||||
public abstract DialogElement start() throws Exception;
|
public abstract DialogElement start() throws Exception;
|
||||||
|
|
||||||
public Dialog evaluateTo(Dialog d) {
|
public Dialog evaluateTo(Dialog d) {
|
||||||
evaluation = () -> d.evaluation.get();
|
evaluation = () -> d.evaluation != null ? d.evaluation.get() : null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,11 @@ public class DataSourceId {
|
||||||
this.entryName = entryName;
|
this.entryName = entryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String cleanString(String input) {
|
||||||
|
var replaced = input.replaceAll(":", "");
|
||||||
|
return replaced.length() == 0 ? "-" : replaced;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new data source id from a collection name and an entry name.
|
* Creates a new data source id from a collection name and an entry name.
|
||||||
*
|
*
|
||||||
|
|
|
@ -41,12 +41,16 @@ public interface DataStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a display string of this store.
|
* Creates a display string of this store.
|
||||||
* This can be a multiline string.
|
|
||||||
*/
|
*/
|
||||||
default String toDisplay() {
|
default String toSummaryString() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default String queryInformationString() throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Casts this instance to the required type without checking whether a cast is possible.
|
* Casts this instance to the required type without checking whether a cast is possible.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
public class DataStoreFormatter {
|
||||||
|
|
||||||
|
public static String ellipsis(String input, int length) {
|
||||||
|
var end = Math.min(input.length(), length);
|
||||||
|
if (end < input.length()) {
|
||||||
|
return input.substring(0, end) + "...";
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String specialFormatHostName(String input) {
|
||||||
|
if (input.contains(":")) {
|
||||||
|
input = input.split(":")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.endsWith(".rds.amazonaws.com")) {
|
||||||
|
var split = input.split("\\.");
|
||||||
|
var name = split[0];
|
||||||
|
var region = split[2];
|
||||||
|
return String.format("RDS %s @ %s", name, region);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,14 @@ import java.nio.file.Path;
|
||||||
public class FileStore implements StreamDataStore, FilenameStore {
|
public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
|
|
||||||
public static FileStore local(Path p) {
|
public static FileStore local(Path p) {
|
||||||
return new FileStore(MachineFileStore.local(), p.toString());
|
return new FileStore(new LocalStore(), p.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a file store for a file that is local to the callers machine.
|
* Creates a file store for a file that is local to the callers machine.
|
||||||
*/
|
*/
|
||||||
public static FileStore local(String p) {
|
public static FileStore local(String p) {
|
||||||
return new FileStore(MachineFileStore.local(), p);
|
return new FileStore(new LocalStore(), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
MachineFileStore machine;
|
MachineFileStore machine;
|
||||||
|
@ -38,7 +38,7 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
@Override
|
@Override
|
||||||
public void validate() throws Exception {
|
public void validate() throws Exception {
|
||||||
if (!machine.exists(file)) {
|
if (!machine.exists(file)) {
|
||||||
throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toDisplay());
|
throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toSummaryString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDisplay() {
|
public String toSummaryString() {
|
||||||
return file + "@" + machine.toDisplay();
|
return file + "@" + machine.toSummaryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,6 +69,10 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFileName() {
|
public String getFileName() {
|
||||||
return file;
|
var split = file.split("[\\\\/]");
|
||||||
|
if (split.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return split[split.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class InMemoryStore implements StreamDataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDisplay() {
|
public String toSummaryString() {
|
||||||
return "inMemory";
|
return "inMemory";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,39 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.xpipe.core.util.Secret;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@JsonTypeName("local")
|
@JsonTypeName("local")
|
||||||
@Value
|
@Value
|
||||||
public class LocalStore implements ShellProcessStore {
|
public class LocalStore extends StandardShellStore implements MachineFileStore {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static class LocalProcessControl extends ProcessControl {
|
static class LocalProcessControl extends ProcessControl {
|
||||||
|
|
||||||
private final InputStream input;
|
private final List<Secret> input;
|
||||||
private final ProcessBuilder builder;
|
private final ProcessBuilder builder;
|
||||||
|
private final Integer timeout;
|
||||||
|
|
||||||
private Process process;
|
private Process process;
|
||||||
|
|
||||||
LocalProcessControl(InputStream input, List<String> cmd) {
|
LocalProcessControl(List<Secret> input, List<String> cmd, Integer timeout) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
|
this.timeout = timeout;
|
||||||
var l = new ArrayList<String>();
|
var l = new ArrayList<String>();
|
||||||
l.add("cmd");
|
l.add("cmd");
|
||||||
l.add("/c");
|
l.add("/c");
|
||||||
|
@ -35,13 +41,19 @@ public class LocalStore implements ShellProcessStore {
|
||||||
builder = new ProcessBuilder(l);
|
builder = new ProcessBuilder(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream createInputStream() {
|
||||||
|
var string = input.stream().map(secret -> secret.getSecretValue()).collect(Collectors.joining("\n")) + "\r\n";
|
||||||
|
return new ByteArrayInputStream(string.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
process = builder.start();
|
process = builder.start();
|
||||||
|
|
||||||
var t = new Thread(() -> {
|
var t = new Thread(() -> {
|
||||||
try {
|
try (var inputStream = createInputStream()){
|
||||||
input.transferTo(process.getOutputStream());
|
process.getOutputStream().flush();
|
||||||
|
inputStream.transferTo(process.getOutputStream());
|
||||||
process.getOutputStream().close();
|
process.getOutputStream().close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
|
@ -53,7 +65,11 @@ public class LocalStore implements ShellProcessStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int waitFor() throws Exception {
|
public int waitFor() throws Exception {
|
||||||
return process.waitFor();
|
if (timeout != null) {
|
||||||
|
return process.waitFor(timeout, TimeUnit.SECONDS) ? 0 : -1;
|
||||||
|
} else {
|
||||||
|
return process.waitFor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,7 +84,11 @@ public class LocalStore implements ShellProcessStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset getCharset() {
|
public Charset getCharset() {
|
||||||
return StandardCharsets.UTF_8;
|
return StandardCharsets.US_ASCII;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTimeout() {
|
||||||
|
return timeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +98,8 @@ public class LocalStore implements ShellProcessStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDisplay() {
|
public String toSummaryString() {
|
||||||
return "local";
|
return "localhost";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,17 +115,17 @@ public class LocalStore implements ShellProcessStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessControl prepareCommand(InputStream input, List<String> cmd) {
|
public ProcessControl prepareCommand(List<Secret> input, List<String> cmd, Integer timeout) {
|
||||||
return new LocalProcessControl(input, cmd);
|
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessControl preparePrivilegedCommand(InputStream input, List<String> cmd) throws Exception {
|
public ProcessControl preparePrivilegedCommand(List<Secret> input, List<String> cmd, Integer timeOut) throws Exception {
|
||||||
return new LocalProcessControl(input, cmd);
|
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShellType determineType() {
|
public ShellType determineType() throws Exception {
|
||||||
return ShellTypes.CMD;
|
return ShellTypes.determine(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import java.io.OutputStream;
|
||||||
|
|
||||||
public interface MachineFileStore extends DataStore {
|
public interface MachineFileStore extends DataStore {
|
||||||
|
|
||||||
static MachineFileStore local() {
|
default boolean isLocal(){
|
||||||
return new LocalStore();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
InputStream openInput(String file) throws Exception;
|
InputStream openInput(String file) throws Exception;
|
||||||
|
|
||||||
OutputStream openOutput(String file) throws Exception;
|
OutputStream openOutput(String file) throws Exception;
|
||||||
|
|
|
@ -33,7 +33,7 @@ public final class NamedStore implements DataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDisplay() {
|
public String toSummaryString() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ public abstract class ProcessControl {
|
||||||
start();
|
start();
|
||||||
var errT = discardErr();
|
var errT = discardErr();
|
||||||
var string = new String(getStdout().readAllBytes(), getCharset());
|
var string = new String(getStdout().readAllBytes(), getCharset());
|
||||||
errT.join();
|
|
||||||
waitFor();
|
waitFor();
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
@ -34,9 +33,6 @@ public abstract class ProcessControl {
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
outT.join();
|
|
||||||
t.join();
|
|
||||||
|
|
||||||
var ec = waitFor();
|
var ec = waitFor();
|
||||||
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
public class ProcessOutputException extends Exception{
|
||||||
|
public ProcessOutputException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessOutputException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessOutputException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessOutputException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProcessOutputException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
package io.xpipe.core.store;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public interface ShellProcessStore extends StandardShellStore {
|
|
||||||
|
|
||||||
ShellType determineType() throws Exception;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default InputStream openInput(String file) throws Exception {
|
|
||||||
var type = determineType();
|
|
||||||
var cmd = type.createFileReadCommand(file);
|
|
||||||
var p = prepareCommand(InputStream.nullInputStream(), cmd);
|
|
||||||
p.start();
|
|
||||||
return p.getStdout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default OutputStream openOutput(String file) throws Exception {
|
|
||||||
return null;
|
|
||||||
// var type = determineType();
|
|
||||||
// var cmd = type.createFileWriteCommand(file);
|
|
||||||
// var p = prepare(cmd).redirectErrorStream(true);
|
|
||||||
// var proc = p.start();
|
|
||||||
// return proc.getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean exists(String file) throws Exception {
|
|
||||||
var type = determineType();
|
|
||||||
var cmd = type.createFileExistsCommand(file);
|
|
||||||
var p = prepareCommand(InputStream.nullInputStream(), cmd);
|
|
||||||
p.start();
|
|
||||||
return p.waitFor() == 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +1,61 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.Secret;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public interface ShellStore extends MachineFileStore {
|
public abstract class ShellStore implements DataStore {
|
||||||
|
|
||||||
static StandardShellStore local() {
|
public Integer getTimeout() {
|
||||||
return new LocalStore();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default String executeAndRead(List<String> cmd) throws Exception {
|
public List<Secret> getInput() {
|
||||||
var pc = prepareCommand(InputStream.nullInputStream(), cmd);
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String executeAndRead(List<String> cmd, Integer timeout) throws Exception {
|
||||||
|
var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout));
|
||||||
pc.start();
|
pc.start();
|
||||||
pc.discardErr();
|
pc.discardErr();
|
||||||
var string = new String(pc.getStdout().readAllBytes(), pc.getCharset());
|
var string = new String(pc.getStdout().readAllBytes(), pc.getCharset());
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
default Optional<String> executeAndCheckOut(InputStream in, List<String> cmd) throws Exception {
|
public String executeAndCheckOut(List<Secret> in, List<String> cmd, Integer timeout) throws ProcessOutputException, Exception {
|
||||||
var pc = prepareCommand(in, cmd);
|
var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout));
|
||||||
pc.start();
|
pc.start();
|
||||||
var outT = pc.discardErr();
|
|
||||||
|
|
||||||
AtomicReference<String> read = new AtomicReference<>();
|
AtomicReference<String> readError = new AtomicReference<>();
|
||||||
|
var errorThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
errorThread.setDaemon(true);
|
||||||
|
errorThread.start();
|
||||||
|
|
||||||
|
var read = new ByteArrayOutputStream();
|
||||||
var t = new Thread(() -> {
|
var t = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
|
final byte[] buf = new byte[1];
|
||||||
|
|
||||||
|
int length;
|
||||||
|
while ((length = pc.getStdout().read(buf)) > 0) {
|
||||||
|
read.write(buf, 0, length);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
|
@ -37,15 +63,21 @@ public interface ShellStore extends MachineFileStore {
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
outT.join();
|
|
||||||
t.join();
|
|
||||||
|
|
||||||
var ec = pc.waitFor();
|
var ec = pc.waitFor();
|
||||||
return ec == 0 ? Optional.of(read.get()) : Optional.empty();
|
var readOut = read.toString(pc.getCharset());
|
||||||
|
if (ec == -1) {
|
||||||
|
throw new ProcessOutputException("Command timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ec == 0) {
|
||||||
|
return readOut;
|
||||||
|
} else {
|
||||||
|
throw new ProcessOutputException("Command returned with " + ec + ": " + readError.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default Optional<String> executeAndCheckErr(InputStream in, List<String> cmd) throws Exception {
|
public Optional<String> executeAndCheckErr(List<Secret> in, List<String> cmd) throws Exception {
|
||||||
var pc = prepareCommand(in, cmd);
|
var pc = prepareCommand(in, cmd, getTimeout());
|
||||||
pc.start();
|
pc.start();
|
||||||
var outT = pc.discardOut();
|
var outT = pc.discardOut();
|
||||||
|
|
||||||
|
@ -67,17 +99,27 @@ public interface ShellStore extends MachineFileStore {
|
||||||
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
default ProcessControl prepareCommand(List<String> cmd) throws Exception {
|
public Integer getEffectiveTimeOut(Integer timeout) {
|
||||||
return prepareCommand(InputStream.nullInputStream(), cmd);
|
if (this.getTimeout() == null) {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
if (timeout == null) {
|
||||||
|
return getTimeout();
|
||||||
|
}
|
||||||
|
return Math.min(getTimeout(), timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessControl prepareCommand(InputStream input, List<String> cmd) throws Exception;
|
public ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception {
|
||||||
|
return prepareCommand(List.of(), cmd, timeout);
|
||||||
default ProcessControl preparePrivilegedCommand(List<String> cmd) throws Exception {
|
|
||||||
return preparePrivilegedCommand(InputStream.nullInputStream(), cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default ProcessControl preparePrivilegedCommand(InputStream input, List<String> cmd) throws Exception {
|
public abstract ProcessControl prepareCommand(List<Secret> input, List<String> cmd, Integer timeout) throws Exception;
|
||||||
|
|
||||||
|
public ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout) throws Exception {
|
||||||
|
return preparePrivilegedCommand(List.of(), cmd, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessControl preparePrivilegedCommand(List<Secret> input, List<String> cmd, Integer timeout) throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.xpipe.core.util.Secret;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -10,28 +12,28 @@ import java.util.List;
|
||||||
public class ShellTypes {
|
public class ShellTypes {
|
||||||
|
|
||||||
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
|
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
|
||||||
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0"));
|
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null).strip();
|
||||||
if (o.isPresent() && !o.get().equals("$0")) {
|
if (!o.equals("$0")) {
|
||||||
return SH;
|
return SH;
|
||||||
} else {
|
} else {
|
||||||
o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"));
|
o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null).trim();
|
||||||
if (o.isPresent() && o.get().equals("PowerShell")) {
|
if (o.equals("PowerShell")) {
|
||||||
return POWERSHELL;
|
return POWERSHELL;
|
||||||
} else {
|
} else
|
||||||
return CMD;
|
return CMD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
||||||
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0"));
|
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
|
||||||
if (o.isPresent() && !o.get().trim().equals("$0")) {
|
if (!o.trim().equals("$0")) {
|
||||||
return getLinuxShells();
|
return getLinuxShells();
|
||||||
} else {
|
} else {
|
||||||
return getWindowsShells();
|
return getWindowsShells();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty("powershell")
|
||||||
public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() {
|
public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,8 +72,14 @@ public class ShellTypes {
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return "PowerShell";
|
return "PowerShell";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
|
return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\"");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@JsonProperty("cmd")
|
||||||
public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() {
|
public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -83,9 +91,9 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception {
|
public ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||||
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
|
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
|
||||||
return st.prepareCommand(InputStream.nullInputStream(), l);
|
return st.prepareCommand(List.of(), l, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,8 +125,14 @@ public class ShellTypes {
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return "cmd.exe";
|
return "cmd.exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
|
return List.of("Get-ComputerInfo");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@JsonProperty("sh")
|
||||||
public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() {
|
public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -127,12 +141,12 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception {
|
public ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||||
var l = new ArrayList<>(cmd);
|
var l = new ArrayList<>(cmd);
|
||||||
l.add(0, "sudo");
|
l.add(0, "sudo");
|
||||||
l.add(1, "-S");
|
l.add(1, "-S");
|
||||||
var pws = new ByteArrayInputStream(pw.getBytes(getCharset()));
|
var pws = new ByteArrayInputStream(pw.getBytes(getCharset()));
|
||||||
return st.prepareCommand(pws, l);
|
return st.prepareCommand(List.of(Secret.createForSecretValue(pw)), l, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -164,6 +178,11 @@ public class ShellTypes {
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return "/bin/sh";
|
return "/bin/sh";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
|
return List.of("uname", "-o");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static StandardShellStore.ShellType getDefault() {
|
public static StandardShellStore.ShellType getDefault() {
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.Secret;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface StandardShellStore extends ShellStore {
|
public abstract class StandardShellStore extends ShellStore implements MachineFileStore {
|
||||||
|
|
||||||
static interface ShellType {
|
|
||||||
|
public static interface ShellType {
|
||||||
|
|
||||||
List<String> switchTo(List<String> cmd);
|
List<String> switchTo(List<String> cmd);
|
||||||
|
|
||||||
default ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception {
|
default ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||||
return st.prepareCommand(in, cmd);
|
return st.prepareCommand(in, cmd, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> createFileReadCommand(String file);
|
List<String> createFileReadCommand(String file);
|
||||||
|
@ -25,7 +29,42 @@ public interface StandardShellStore extends ShellStore {
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
String getDisplayName();
|
String getDisplayName();
|
||||||
|
|
||||||
|
List<String> getOperatingSystemNameCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShellType determineType() throws Exception;
|
public abstract ShellType determineType() throws Exception;
|
||||||
|
|
||||||
|
public final String querySystemName() throws Exception {
|
||||||
|
var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout());
|
||||||
|
return result.strip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream openInput(String file) throws Exception {
|
||||||
|
var type = determineType();
|
||||||
|
var cmd = type.createFileReadCommand(file);
|
||||||
|
var p = prepareCommand(List.of(), cmd, null);
|
||||||
|
p.start();
|
||||||
|
return p.getStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutput(String file) throws Exception {
|
||||||
|
return null;
|
||||||
|
// var type = determineType();
|
||||||
|
// var cmd = type.createFileWriteCommand(file);
|
||||||
|
// var p = prepare(cmd).redirectErrorStream(true);
|
||||||
|
// var proc = p.start();
|
||||||
|
// return proc.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(String file) throws Exception {
|
||||||
|
var type = determineType();
|
||||||
|
var cmd = type.createFileExistsCommand(file);
|
||||||
|
var p = prepareCommand(List.of(), cmd, null);
|
||||||
|
p.start();
|
||||||
|
return p.waitFor() == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ module io.xpipe.core {
|
||||||
opens io.xpipe.core.data.typed;
|
opens io.xpipe.core.data.typed;
|
||||||
opens io.xpipe.core.dialog;
|
opens io.xpipe.core.dialog;
|
||||||
|
|
||||||
requires com.fasterxml.jackson.core;
|
requires static com.fasterxml.jackson.core;
|
||||||
requires com.fasterxml.jackson.databind;
|
requires static com.fasterxml.jackson.databind;
|
||||||
requires java.net.http;
|
requires java.net.http;
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
|
|
||||||
|
|
1
deps
1
deps
|
@ -1 +0,0 @@
|
||||||
Subproject commit 49a1ad06bc6872f72c1d20ea864d24f3df59b7c5
|
|
|
@ -25,17 +25,13 @@ version = file('../misc/version').text
|
||||||
group = 'io.xpipe'
|
group = 'io.xpipe'
|
||||||
archivesBaseName = 'extension'
|
archivesBaseName = 'extension'
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly 'net.synedra:validatorfx:0.3.1'
|
compileOnly 'net.synedra:validatorfx:0.3.1'
|
||||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
|
|
||||||
implementation project(':fxcomps')
|
// implementation project(':fxcomps')
|
||||||
//implementation 'io.xpipe:fxcomps:0.2'
|
implementation 'io.xpipe:fxcomps:0.2.1'
|
||||||
implementation 'org.controlsfx:controlsfx:11.1.1'
|
implementation 'org.controlsfx:controlsfx:11.1.1'
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import java.util.Map;
|
||||||
|
|
||||||
public interface DataSourceProvider<T extends DataSource<?>> {
|
public interface DataSourceProvider<T extends DataSource<?>> {
|
||||||
|
|
||||||
static enum GeneralType {
|
static enum Category {
|
||||||
FILE,
|
FILE,
|
||||||
DATABASE;
|
DATABASE;
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,13 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
||||||
getSourceClass();
|
getSourceClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
default GeneralType getGeneralType() {
|
default Category getGeneralType() {
|
||||||
if (getFileProvider() != null) {
|
if (getFileProvider() != null) {
|
||||||
return GeneralType.FILE;
|
return Category.FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getDatabaseProvider() != null) {
|
if (getDatabaseProvider() != null) {
|
||||||
return GeneralType.DATABASE;
|
return Category.DATABASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ExtensionException("Provider has no general type");
|
throw new ExtensionException("Provider has no general type");
|
||||||
|
@ -56,7 +56,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
||||||
return getId() + "." + key;
|
return getId() + "." + key;
|
||||||
}
|
}
|
||||||
|
|
||||||
default Region createConfigGui(Property<T> source) {
|
default Region createConfigGui(Property<T> source, Property<T> appliedSource) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import io.xpipe.core.dialog.Dialog;
|
import io.xpipe.core.dialog.Dialog;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.*;
|
||||||
import io.xpipe.core.store.MachineFileStore;
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.core.store.StreamDataStore;
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -35,9 +32,14 @@ public interface DataStoreProvider {
|
||||||
throw new ExtensionException("Gui Dialog is not implemented by provider " + getId());
|
throw new ExtensionException("Gui Dialog is not implemented by provider " + getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
default void init() throws Exception {
|
default boolean init() throws Exception {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String queryInformationString(DataStore store, int length) throws Exception;
|
||||||
|
|
||||||
|
public String toSummaryString(DataStore store, int length);
|
||||||
|
|
||||||
default boolean isHidden() {
|
default boolean isHidden() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +83,7 @@ public interface DataStoreProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
default String display(DataStore store) {
|
default String display(DataStore store) {
|
||||||
return store.toDisplay();
|
return store.toSummaryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getPossibleNames();
|
List<String> getPossibleNames();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import io.xpipe.core.dialog.Dialog;
|
import io.xpipe.core.dialog.Dialog;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -53,6 +54,11 @@ public class DataStoreProviders {
|
||||||
return ALL.stream().map(d -> d.dialogForString(s)).filter(Objects::nonNull).findAny();
|
return ALL.stream().map(d -> d.dialogForString(s)).filter(Objects::nonNull).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <T extends DataStoreProvider> T byStore(DataStore store) {
|
||||||
|
return byStoreClass(store.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) {
|
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) {
|
||||||
if (ALL == null) {
|
if (ALL == null) {
|
||||||
|
|
|
@ -1,28 +1,57 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import java.util.function.Supplier;
|
import lombok.Value;
|
||||||
|
|
||||||
public interface SupportedApplicationProvider {
|
public interface SupportedApplicationProvider {
|
||||||
|
|
||||||
enum Category {
|
enum Category {
|
||||||
PROGRAMMING_LANGUAGE,
|
PROGRAMMING_LANGUAGE,
|
||||||
APPLICATION
|
APPLICATION,
|
||||||
|
OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
Region createRetrieveInstructions(ObservableValue<String> id);
|
enum AccessType {
|
||||||
|
ACTIVE,
|
||||||
|
PASSIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class InstructionsDisplay {
|
||||||
|
Region region;
|
||||||
|
Runnable onFinish;
|
||||||
|
|
||||||
|
public InstructionsDisplay(Region region) {
|
||||||
|
this.region = region;
|
||||||
|
onFinish = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default InstructionsDisplay createRetrievalInstructions(DataSource<?> source, ObservableValue<String> id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default InstructionsDisplay createUpdateInstructions(DataSource<?> source, ObservableValue<String> id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
Supplier<String> getName();
|
ObservableValue<String> getName();
|
||||||
|
|
||||||
Category getCategory();
|
Category getCategory();
|
||||||
|
|
||||||
|
AccessType getAccessType();
|
||||||
|
|
||||||
String getSetupGuideURL();
|
String getSetupGuideURL();
|
||||||
|
|
||||||
default String getGraphicIcon() {
|
default String getGraphicIcon() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default Dialog configDialog(T source, boolean all) {
|
default Dialog configDialog(T source, boolean all) {
|
||||||
return Dialog.empty();
|
return Dialog.empty().evaluateTo(() -> source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,7 +19,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
|
||||||
try {
|
try {
|
||||||
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
|
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
throw new AssertionError(e);
|
throw new ExtensionException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import net.synedra.validatorfx.Check;
|
import net.synedra.validatorfx.Check;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class Validators {
|
public class Validators {
|
||||||
|
|
||||||
public static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) {
|
public static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) {
|
||||||
|
@ -12,4 +15,16 @@ public class Validators {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void nonNull(Object object, String name) {
|
||||||
|
if (object == null) {
|
||||||
|
throw new IllegalArgumentException(I18n.get("extension.null", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) {
|
||||||
|
if (!predicate.test(host)) {
|
||||||
|
throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,42 +4,33 @@ import io.xpipe.fxcomps.Comp;
|
||||||
import io.xpipe.fxcomps.CompStructure;
|
import io.xpipe.fxcomps.CompStructure;
|
||||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import org.apache.commons.collections4.BidiMap;
|
|
||||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CharChoiceComp extends Comp<CompStructure<HBox>> {
|
public class CharChoiceComp extends Comp<CompStructure<HBox>> {
|
||||||
|
|
||||||
private final Property<Character> value;
|
private final Property<Character> value;
|
||||||
private final Property<Character> charChoiceValue;
|
private final Map<Character, ObservableValue<String>> range;
|
||||||
private final BidiMap<Character, ObservableValue<String>> range;
|
|
||||||
private final ObservableValue<String> customName;
|
private final ObservableValue<String> customName;
|
||||||
|
|
||||||
public CharChoiceComp(Property<Character> value, BidiMap<Character, ObservableValue<String>> range, ObservableValue<String> customName) {
|
public CharChoiceComp(Property<Character> value, Map<Character, ObservableValue<String>> range, ObservableValue<String> customName) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
this.customName = customName;
|
this.customName = customName;
|
||||||
this.charChoiceValue = new SimpleObjectProperty<>(range.containsKey(value.getValue()) ? value.getValue() : null);
|
|
||||||
value.addListener((c, o, n) -> {
|
|
||||||
if (!range.containsKey(n)) {
|
|
||||||
charChoiceValue.setValue(null);
|
|
||||||
} else {
|
|
||||||
charChoiceValue.setValue(n);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<HBox> createBase() {
|
public CompStructure<HBox> createBase() {
|
||||||
var charChoice = new CharComp(value);
|
var charChoice = new CharComp(value);
|
||||||
var rangeCopy = new DualLinkedHashBidiMap<>(range);
|
var rangeCopy = new HashMap<>(range);
|
||||||
if (customName != null) {
|
if (customName != null) {
|
||||||
rangeCopy.put(null, customName);
|
rangeCopy.put(null, customName);
|
||||||
}
|
}
|
||||||
var choice = new ChoiceComp<Character>(charChoiceValue, rangeCopy);
|
var choice = new ChoiceComp<Character>(value, rangeCopy);
|
||||||
var charChoiceR = charChoice.createRegion();
|
var charChoiceR = charChoice.createRegion();
|
||||||
var choiceR = choice.createRegion();
|
var choiceR = choice.createRegion();
|
||||||
var box = new HBox(charChoiceR, choiceR);
|
var box = new HBox(charChoiceR, choiceR);
|
||||||
|
|
|
@ -7,10 +7,11 @@ import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
|
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
|
||||||
|
|
||||||
|
@ -23,9 +24,10 @@ public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Ch
|
||||||
@Override
|
@Override
|
||||||
protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
|
protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
|
||||||
var map = new LinkedHashMap<Charset, ObservableValue<String>>();
|
var map = new LinkedHashMap<Charset, ObservableValue<String>>();
|
||||||
for (var e : Charset.availableCharsets().entrySet()) {
|
for (var e : List.of(StandardCharsets.UTF_8, StandardCharsets.UTF_16,
|
||||||
map.put(e.getValue(), new SimpleStringProperty(e.getKey()));
|
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()));
|
||||||
}
|
}
|
||||||
return new ChoiceComp<>(charset, new DualLinkedHashBidiMap<>(map));
|
return new ChoiceComp<>(charset, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,42 +5,65 @@ import io.xpipe.fxcomps.Comp;
|
||||||
import io.xpipe.fxcomps.CompStructure;
|
import io.xpipe.fxcomps.CompStructure;
|
||||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.fxcomps.util.PlatformThread;
|
import io.xpipe.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.fxcomps.util.SimpleChangeListener;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.apache.commons.collections4.BidiMap;
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Value
|
||||||
public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
||||||
|
|
||||||
private final Property<T> value;
|
Property<T> value;
|
||||||
private final BidiMap<T, ObservableValue<String>> range;
|
ObservableValue<Map<T, ObservableValue<String>>> range;
|
||||||
|
|
||||||
public ChoiceComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) {
|
public ChoiceComp(Property<T> value, Map<T, ObservableValue<String>> range) {
|
||||||
|
this.value = value;
|
||||||
|
this.range = new SimpleObjectProperty<>(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChoiceComp(Property<T> value, ObservableValue<Map<T, ObservableValue<String>>> range) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<ComboBox<T>> createBase() {
|
public CompStructure<ComboBox<T>> createBase() {
|
||||||
var list = FXCollections.observableArrayList(range.keySet());
|
var cb = new ComboBox<T>();
|
||||||
var cb = new ComboBox<>(list);
|
cb.setConverter(new StringConverter<T>() {
|
||||||
cb.setConverter(new StringConverter<>() {
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(T object) {
|
public String toString(T object) {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return I18n.get("extension.none");
|
return I18n.get("extension.none");
|
||||||
}
|
}
|
||||||
|
|
||||||
return range.get(object).getValue();
|
var found = range.getValue().get(object);
|
||||||
|
if (found == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return found.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T fromString(String string) {
|
public T fromString(String string) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
SimpleChangeListener.apply(PlatformThread.sync(range), c -> {
|
||||||
|
|
||||||
|
var list = FXCollections.observableArrayList(c.keySet());
|
||||||
|
if (!list.contains(null)) {
|
||||||
|
list.add(null);
|
||||||
|
}
|
||||||
|
cb.setItems(list);
|
||||||
|
});
|
||||||
PlatformThread.connect(value, cb.valueProperty());
|
PlatformThread.connect(value, cb.valueProperty());
|
||||||
cb.getStyleClass().add("choice-comp");
|
cb.getStyleClass().add("choice-comp");
|
||||||
return new SimpleCompStructure<>(cb);
|
return new SimpleCompStructure<>(cb);
|
||||||
|
|
|
@ -11,7 +11,6 @@ import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import net.synedra.validatorfx.Check;
|
import net.synedra.validatorfx.Check;
|
||||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -24,6 +23,7 @@ public class DynamicOptionsBuilder<T> {
|
||||||
|
|
||||||
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
|
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
|
||||||
private final List<Property<?>> props = new ArrayList<>();
|
private final List<Property<?>> props = new ArrayList<>();
|
||||||
|
private final List<Property<?>> lazyProperties = new ArrayList<>();
|
||||||
|
|
||||||
private final ObservableValue<String> title;
|
private final ObservableValue<String> title;
|
||||||
private final boolean wrap;
|
private final boolean wrap;
|
||||||
|
@ -43,7 +43,15 @@ public class DynamicOptionsBuilder<T> {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicOptionsBuilder<T> makeLazy() {
|
||||||
|
var p = props.get(props.size() - 1);
|
||||||
|
props.remove(p);
|
||||||
|
lazyProperties.add(p);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> decorate(Check c) {
|
public DynamicOptionsBuilder<T> decorate(Check c) {
|
||||||
|
|
||||||
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
|
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -59,28 +67,42 @@ public class DynamicOptionsBuilder<T> {
|
||||||
for (var e : NewLine.values()) {
|
for (var e : NewLine.values()) {
|
||||||
map.put(e, I18n.observable("extension." + e.getId()));
|
map.put(e, I18n.observable("extension." + e.getId()));
|
||||||
}
|
}
|
||||||
var comp = new ChoiceComp<>(prop, new DualLinkedHashBidiMap<>(map));
|
var comp = new ChoiceComp<>(prop,map);
|
||||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
||||||
props.add(prop);
|
props.add(prop);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names) {
|
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names) {
|
||||||
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), null);
|
var comp = new CharChoiceComp(prop, names, null);
|
||||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
props.add(prop);
|
props.add(prop);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names, ObservableValue<String> customName) {
|
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names, ObservableValue<String> customName) {
|
||||||
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), customName);
|
var comp = new CharChoiceComp(prop, names, customName);
|
||||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
props.add(prop);
|
props.add(prop);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <V> DynamicOptionsBuilder<T> addToggle(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
|
public <V> DynamicOptionsBuilder<T> addToggle(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
|
||||||
var comp = new ToggleGroupComp<>(prop, new DualLinkedHashBidiMap<>(names));
|
var comp = new ToggleGroupComp<>(prop, names);
|
||||||
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
|
props.add(prop);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V> DynamicOptionsBuilder<T> addChoice(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
|
||||||
|
var comp = new ChoiceComp<>(prop, names);
|
||||||
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
|
props.add(prop);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V> DynamicOptionsBuilder<T> addChoice(Property<V> prop, ObservableValue<String> name, ObservableValue<Map<V, ObservableValue<String>>> names) {
|
||||||
|
var comp = new ChoiceComp<>(prop, names);
|
||||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
props.add(prop);
|
props.add(prop);
|
||||||
return this;
|
return this;
|
||||||
|
@ -107,6 +129,14 @@ public class DynamicOptionsBuilder<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicOptionsBuilder<T> addLazyString(String nameKey, Property<String> prop, Property<String> lazy) {
|
||||||
|
var comp = new TextFieldComp(prop, lazy);
|
||||||
|
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
|
||||||
|
props.add(prop);
|
||||||
|
lazyProperties.add(lazy);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
|
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
|
||||||
var comp = new TextFieldComp(prop);
|
var comp = new TextFieldComp(prop);
|
||||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
|
@ -120,6 +150,10 @@ public class DynamicOptionsBuilder<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicOptionsBuilder<T> addSecret(String nameKey, Property<Secret> prop) {
|
||||||
|
return addSecret(I18n.observable(nameKey), prop);
|
||||||
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addSecret(ObservableValue<String> name, Property<Secret> prop) {
|
public DynamicOptionsBuilder<T> addSecret(ObservableValue<String> name, Property<Secret> prop) {
|
||||||
var comp = new SecretFieldComp(prop);
|
var comp = new SecretFieldComp(prop);
|
||||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||||
|
@ -134,20 +168,61 @@ public class DynamicOptionsBuilder<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <V extends T> Comp<?> buildComp(Supplier<V> creator, Property<T> toSet) {
|
public DynamicOptionsBuilder<T> addInteger(String nameKey, Property<Integer> prop) {
|
||||||
|
var comp = new IntFieldComp(prop);
|
||||||
|
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
|
||||||
|
props.add(prop);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <V extends T> DynamicOptionsBuilder<T> bind(Supplier<V> creator, Property<T>... toSet) {
|
||||||
props.forEach(prop -> {
|
props.forEach(prop -> {
|
||||||
prop.addListener((c,o,n) -> {
|
prop.addListener((c, o, n) -> {
|
||||||
toSet.setValue(creator.get());
|
for (Property<T> p : toSet) {
|
||||||
|
p.setValue(creator.get());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
toSet.setValue(creator.get());
|
for (Property<T> p : toSet) {
|
||||||
|
p.setValue(creator.get());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends T> DynamicOptionsBuilder<T> bindLazy(Supplier<V> creator, Property<T> toLazySet) {
|
||||||
|
lazyProperties.forEach(prop -> {
|
||||||
|
prop.addListener((c,o,n) -> {
|
||||||
|
toLazySet.setValue(creator.get());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
toLazySet.setValue(creator.get());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends T> Comp<?> buildComp() {
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
||||||
}
|
}
|
||||||
return new DynamicOptionsComp(entries, wrap);
|
return new DynamicOptionsComp(entries, wrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <V extends T> Region build(Supplier<V> creator, Property<T> toSet) {
|
public <V extends T> Comp<?> buildBindingComp(Supplier<Property<? extends V>> creator, Property<T> toSet) {
|
||||||
return buildComp(creator, toSet).createRegion();
|
props.forEach(prop -> {
|
||||||
|
prop.addListener((c,o,n) -> {
|
||||||
|
toSet.unbind();
|
||||||
|
toSet.bind(creator.get());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
toSet.bind(creator.get());
|
||||||
|
if (title != null) {
|
||||||
|
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
||||||
|
}
|
||||||
|
return new DynamicOptionsComp(entries, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends T> Region build() {
|
||||||
|
return buildComp().createRegion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
||||||
var text = new PasswordField();
|
var text = new PasswordField();
|
||||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||||
text.textProperty().addListener((c, o, n) -> {
|
text.textProperty().addListener((c, o, n) -> {
|
||||||
value.setValue(n.length() > 0 ? Secret.createForSecretValue(n) : null);
|
value.setValue(n != null && n.length() > 0 ? Secret.createForSecretValue(n) : null);
|
||||||
});
|
});
|
||||||
value.addListener((c, o, n) -> {
|
value.addListener((c, o, n) -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
|
|
|
@ -25,7 +25,10 @@ public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
|
||||||
|
|
||||||
for (var e : entries) {
|
for (var e : entries) {
|
||||||
Tab tab = new Tab();
|
Tab tab = new Tab();
|
||||||
var ll = new Label(null, new FontIcon(e.graphic()));
|
var ll = new Label(null);
|
||||||
|
if (e.graphic != null) {
|
||||||
|
ll.setGraphic(new FontIcon(e.graphic()));
|
||||||
|
}
|
||||||
ll.textProperty().bind(e.name());
|
ll.textProperty().bind(e.name());
|
||||||
ll.getStyleClass().add("name");
|
ll.getStyleClass().add("name");
|
||||||
ll.setAlignment(Pos.CENTER);
|
ll.setAlignment(Pos.CENTER);
|
||||||
|
|
|
@ -5,14 +5,25 @@ import io.xpipe.fxcomps.CompStructure;
|
||||||
import io.xpipe.fxcomps.SimpleCompStructure;
|
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.fxcomps.util.PlatformThread;
|
import io.xpipe.fxcomps.util.PlatformThread;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
|
||||||
public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||||
|
|
||||||
private final Property<String> value;
|
private final Property<String> value;
|
||||||
|
private final Property<String> lazyValue;
|
||||||
|
|
||||||
public TextFieldComp(Property<String> value) {
|
public TextFieldComp(Property<String> value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.lazyValue = value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextFieldComp(Property<String> value, Property<String> lazyValue) {
|
||||||
|
this.value = value;
|
||||||
|
this.lazyValue = lazyValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,6 +37,15 @@ public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||||
text.setText(n);
|
text.setText(n);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
text.setOnKeyPressed(new EventHandler<KeyEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(KeyEvent ke) {
|
||||||
|
if (ke.getCode().equals(KeyCode.ENTER)) {
|
||||||
|
lazyValue.setValue(value.getValue());
|
||||||
|
}
|
||||||
|
ke.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
return new SimpleCompStructure<>(text);
|
return new SimpleCompStructure<>(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,22 +9,19 @@ import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import org.apache.commons.collections4.BidiMap;
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
||||||
|
|
||||||
private final Property<T> value;
|
private final Property<T> value;
|
||||||
private final BidiMap<T, ObservableValue<String>> range;
|
private final Map<T, ObservableValue<String>> range;
|
||||||
|
|
||||||
public ToggleGroupComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) {
|
public ToggleGroupComp(Property<T> value, Map<T, ObservableValue<String>> range) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BidiMap<T, ObservableValue<String>> getRange() {
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<HBox> createBase() {
|
public CompStructure<HBox> createBase() {
|
||||||
var box = new HBox();
|
var box = new HBox();
|
||||||
|
|
|
@ -33,6 +33,11 @@ public class ErrorEvent {
|
||||||
.description(msg);
|
.description(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ErrorEventBuilder fromMessage(String msg) {
|
||||||
|
return builder()
|
||||||
|
.description(msg);
|
||||||
|
}
|
||||||
|
|
||||||
public void handle() {
|
public void handle() {
|
||||||
EventHandler.get().handle(this);
|
EventHandler.get().handle(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,45 @@ package io.xpipe.extension.prefs;
|
||||||
|
|
||||||
import io.xpipe.extension.I18n;
|
import io.xpipe.extension.I18n;
|
||||||
import io.xpipe.extension.Translatable;
|
import io.xpipe.extension.Translatable;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface PrefsChoiceValue extends Translatable {
|
public interface PrefsChoiceValue extends Translatable {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@SneakyThrows
|
||||||
|
static <T> List<T> getAll(Class<T> type) {
|
||||||
|
if (Enum.class.isAssignableFrom(type)) {
|
||||||
|
return Arrays.asList(type.getEnumConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
type.getDeclaredField("ALL");
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (List<T>) type.getDeclaredField("ALL").get(null);
|
||||||
|
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||||
|
return List.of(type.getEnumConstants());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends PrefsChoiceValue> List<T> getSupported(Class<T> type) {
|
||||||
|
try {
|
||||||
|
return (List<T>) type.getDeclaredField("SUPPORTED").get(null);
|
||||||
|
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||||
|
return getAll(type).stream().filter(t -> t.isSupported()).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String toTranslatedString() {
|
default String toTranslatedString() {
|
||||||
return I18n.get(getId());
|
return I18n.get(getId());
|
||||||
|
|
|
@ -13,7 +13,6 @@ module io.xpipe.extension {
|
||||||
requires javafx.graphics;
|
requires javafx.graphics;
|
||||||
requires transitive javafx.controls;
|
requires transitive javafx.controls;
|
||||||
requires io.xpipe.fxcomps;
|
requires io.xpipe.fxcomps;
|
||||||
requires static org.apache.commons.collections4;
|
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
requires static com.dlsc.preferencesfx;
|
requires static com.dlsc.preferencesfx;
|
||||||
requires static com.dlsc.formsfx;
|
requires static com.dlsc.formsfx;
|
||||||
|
|
|
@ -4,4 +4,9 @@ crlf=CRLF (Windows)
|
||||||
lf=LF (Linux)
|
lf=LF (Linux)
|
||||||
none=None
|
none=None
|
||||||
nullPointer=Null Pointer: $MSG$
|
nullPointer=Null Pointer: $MSG$
|
||||||
mustNotBeEmpty=$NAME$ must not be empty
|
mustNotBeEmpty=$NAME$ must not be empty
|
||||||
|
null=$VALUE$ must be not null
|
||||||
|
hostFeatureUnsupported=Host does not support the feature $FEATURE$
|
||||||
|
namedHostFeatureUnsupported=$HOST$ does not support this feature
|
||||||
|
namedHostNotActive=$HOST$ is not active
|
||||||
|
noInformationAvailable=No information available
|
0
gradlew
vendored
Executable file → Normal file
0
gradlew
vendored
Executable file → Normal file
|
@ -5,8 +5,8 @@ def canonicalVersion = file('misc/canonical_version').text
|
||||||
|
|
||||||
jreleaser {
|
jreleaser {
|
||||||
environment {
|
environment {
|
||||||
properties.put('rawChangelog', file("misc/changelogs/${canonicalVersion}.txt").exists() ?
|
properties.put('rawChangelog', file("misc/changelogs/${version}.txt").exists() ?
|
||||||
file("misc/changelogs/${canonicalVersion}.txt").text.replace('\r\n', '\n') : "")
|
file("misc/changelogs/${version}.txt").text.replace('\r\n', '\n') : "")
|
||||||
}
|
}
|
||||||
|
|
||||||
project {
|
project {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
0.1
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
🚀 {{projectName}} {{projectVersion}} has been released: {{releaseNotesUrl}}
|
🚀 {{projectName}} {{projectVersion}} has been released: {{releaseNotesUrl}}
|
||||||
|
|
||||||
If you have already installed {{projectName}} and have updates enabled,
|
The documentation and maven repositories should be automatically updated within the next couple of hours.
|
||||||
this update will be automatically installed when launching it the next time.
|
|
||||||
|
|
||||||
You can get the standalone version here: {{releaseNotesUrl}}
|
|
||||||
|
|
||||||
Changes in {{projectVersion}}:
|
Changes in {{projectVersion}}:
|
||||||
{{{rawChangelog}}}
|
{{{rawChangelog}}}
|
|
@ -5,8 +5,7 @@
|
||||||
Note that as this is not a final release, there might still be some small issues with it.
|
Note that as this is not a final release, there might still be some small issues with it.
|
||||||
Please report them if you stumble upon one.
|
Please report them if you stumble upon one.
|
||||||
|
|
||||||
Also note that pre-releases are not downloaded by the automatic updater
|
The documentation and maven repositories should be automatically updated within the next couple of hours.
|
||||||
until they are ready for a full release or you have opted in to receiving pre-releases.
|
|
||||||
|
|
||||||
Changes in {{projectVersion}}:
|
Changes in {{projectVersion}}:
|
||||||
{{{rawChangelog}}}
|
{{{rawChangelog}}}
|
|
@ -1 +1 @@
|
||||||
0.1-SNAPSHOT
|
0.0.1-SNAPSHOT
|
|
@ -4,3 +4,12 @@ include 'api'
|
||||||
include 'core'
|
include 'core'
|
||||||
include 'beacon'
|
include 'beacon'
|
||||||
include 'extension'
|
include 'extension'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue