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
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,3 +4,5 @@ build/
|
|||
dev.properties
|
||||
extensions.txt
|
||||
local/
|
||||
.vscode
|
||||
bin
|
|
@ -7,6 +7,7 @@ plugins {
|
|||
|
||||
apply from: "$rootDir/deps/java.gradle"
|
||||
apply from: "$rootDir/deps/junit.gradle"
|
||||
apply from: "$rootDir/deps/jackson.gradle"
|
||||
apply from: 'publish.gradle'
|
||||
apply from: "$rootDir/deps/publish-base.gradle"
|
||||
|
||||
|
@ -19,18 +20,18 @@ repositories {
|
|||
}
|
||||
|
||||
test {
|
||||
enabled = false
|
||||
enabled = true
|
||||
}
|
||||
|
||||
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(':beacon')
|
||||
}
|
||||
|
||||
configurations {
|
||||
testImplementation.extendsFrom(dep)
|
||||
}
|
||||
|
||||
def canTestWithDev = findProject(':app') != null
|
||||
def home = System.getenv('XPIPE_HOME')
|
||||
String daemonCommand = canTestWithDev ?
|
||||
|
@ -52,7 +53,7 @@ test {
|
|||
" -Dio.xpipe.app.logLevel=trace"
|
||||
|
||||
// API properties
|
||||
// systemProperty 'io.xpipe.beacon.debugOutput', "true"
|
||||
systemProperty 'io.xpipe.beacon.debugOutput', "true"
|
||||
systemProperty 'io.xpipe.beacon.debugExecOutput', "true"
|
||||
systemProperty "io.xpipe.beacon.port", "21722"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
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.InputStream;
|
||||
|
@ -9,7 +11,6 @@ import java.io.UncheckedIOException;
|
|||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return create(null, type, config, path);
|
||||
public static DataSource createAnonymous(String type, Path 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)) {
|
||||
return create(id, type, config, in);
|
||||
return create(id, type, in);
|
||||
} catch (IOException 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) {
|
||||
return create(null, type, config, url);
|
||||
public static DataSource createAnonymous(String type, URL 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()) {
|
||||
return create(id, type, config, in);
|
||||
return create(id, type, in);
|
||||
} catch (IOException 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) {
|
||||
return create(null, type, config, in);
|
||||
public static DataSource createAnonymous(String type, InputStream in) {
|
||||
return create(null, type, in);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,12 +134,11 @@ public interface DataSource {
|
|||
*
|
||||
* @param id the data source id
|
||||
* @param type the data source type
|
||||
* @param config additional configuration options for the specific data source type
|
||||
* @param in the input stream to read
|
||||
* @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) {
|
||||
return DataSourceImpl.create(id, type, config, in);
|
||||
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package io.xpipe.api.connector;
|
||||
|
||||
import io.xpipe.beacon.*;
|
||||
import io.xpipe.beacon.exchange.cli.DialogExchange;
|
||||
import io.xpipe.core.dialog.DialogReference;
|
||||
import io.xpipe.core.util.JacksonHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
@ -13,6 +15,22 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
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) {
|
||||
try (var con = new XPipeConnection()) {
|
||||
con.constructSocket();
|
||||
|
@ -74,7 +92,7 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
}
|
||||
|
||||
public static Optional<BeaconClient> waitForStartup() {
|
||||
for (int i = 0; i < 40; i++) {
|
||||
for (int i = 0; i < 80; i++) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ignored) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import io.xpipe.core.source.DataSourceId;
|
|||
import io.xpipe.core.source.DataSourceReference;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class DataSourceImpl implements DataSource {
|
||||
|
||||
|
@ -19,29 +18,30 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
||||
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
||||
var config = new DataSourceConfig(res.getProvider(), res.getConfig());
|
||||
switch (res.getInfo().getType()) {
|
||||
return switch (res.getInfo().getType()) {
|
||||
case TABLE -> {
|
||||
var data = res.getInfo().asTable();
|
||||
return new DataTableImpl(res.getId(), config, data);
|
||||
yield new DataTableImpl(res.getId(), config, data);
|
||||
}
|
||||
case STRUCTURE -> {
|
||||
var info = res.getInfo().asStructure();
|
||||
return new DataStructureImpl(res.getId(), config, info);
|
||||
yield new DataStructureImpl(res.getId(), config, info);
|
||||
}
|
||||
case TEXT -> {
|
||||
var info = res.getInfo().asText();
|
||||
return new DataTextImpl(res.getId(), config, info);
|
||||
yield new DataTextImpl(res.getId(), config, info);
|
||||
}
|
||||
case RAW -> {
|
||||
var info = res.getInfo().asRaw();
|
||||
return new DataRawImpl(res.getId(), config, info);
|
||||
yield new DataRawImpl(res.getId(), config, info);
|
||||
}
|
||||
}
|
||||
throw new AssertionError();
|
||||
case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getInfo().getType());
|
||||
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 req = StoreStreamExchange.Request.builder().build();
|
||||
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()
|
||||
.provider(type)
|
||||
.store(store)
|
||||
.target(id)
|
||||
.configureAll(false)
|
||||
.build();
|
||||
var startRes = XPipeConnection.execute(con -> {
|
||||
ReadExchange.Response r = con.performSimpleExchange(startReq);
|
||||
|
@ -60,13 +62,8 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
});
|
||||
|
||||
var configInstance = startRes.getConfig();
|
||||
//TODO
|
||||
// configInstance.getConfigInstance().getCurrentValues().putAll(config);
|
||||
// var endReq = ReadExecuteExchange.Request.builder()
|
||||
// .target(id).dataStore(store).config(configInstance).build();
|
||||
// XPipeConnection.execute(con -> {
|
||||
// con.performSimpleExchange(endReq);
|
||||
// });
|
||||
XPipeConnection.finishDialog(configInstance);
|
||||
|
||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||
return get(ref);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import io.xpipe.api.DataTable;
|
|||
import io.xpipe.api.DataTableAccumulator;
|
||||
import io.xpipe.api.connector.XPipeConnection;
|
||||
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.core.data.node.DataStructureNode;
|
||||
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.DataSourceReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
|
@ -23,25 +26,35 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
|||
private final TupleType type;
|
||||
private int rows;
|
||||
private TupleType writtenDescriptor;
|
||||
private OutputStream bodyOutput;
|
||||
|
||||
public DataTableAccumulatorImpl(TupleType type) {
|
||||
this.type = type;
|
||||
connection = XPipeConnection.open();
|
||||
connection.sendRequest(StoreStreamExchange.Request.builder().build());
|
||||
connection.sendBody();
|
||||
bodyOutput = connection.sendBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
connection.close();
|
||||
|
||||
// var req = ReadExecuteExchange.Request.builder()
|
||||
// .target(id).dataStore(res.getStore()).build();
|
||||
// XPipeConnection.execute(con -> {
|
||||
// con.performSimpleExchange(req);
|
||||
// });
|
||||
var req = ReadExchange.Request.builder()
|
||||
.target(id).store(res.getStore()).provider("xpbt").configureAll(false).build();
|
||||
ReadExchange.Response response = XPipeConnection.execute(con -> {
|
||||
return con.performSimpleExchange(req);
|
||||
});
|
||||
|
||||
var configInstance = response.getConfig();
|
||||
XPipeConnection.finishDialog(configInstance);
|
||||
|
||||
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.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DataTableTest extends DaemonControl {
|
||||
|
||||
@BeforeAll
|
||||
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
|
||||
|
|
|
@ -15,8 +15,8 @@ module io.xpipe.beacon {
|
|||
opens io.xpipe.beacon.exchange.data;
|
||||
opens io.xpipe.beacon.exchange.cli;
|
||||
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires static com.fasterxml.jackson.core;
|
||||
requires static com.fasterxml.jackson.databind;
|
||||
requires transitive io.xpipe.core;
|
||||
requires static lombok;
|
||||
|
||||
|
|
|
@ -3,9 +3,7 @@ plugins {
|
|||
}
|
||||
|
||||
if(project == rootProject) {
|
||||
plugins {
|
||||
id "io.codearte.nexus-staging" version "0.30.0"
|
||||
}
|
||||
apply plugin: "io.codearte.nexus-staging"
|
||||
}
|
||||
|
||||
version file('misc/version').text
|
||||
|
|
|
@ -24,3 +24,7 @@ archivesBaseName = 'core'
|
|||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies{
|
||||
compileOnly 'org.apache.commons:commons-exec:1.3'
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
|||
}
|
||||
|
||||
public boolean isNull() {
|
||||
throw unsupported("null check");
|
||||
return false;
|
||||
}
|
||||
|
||||
public final ValueNode asValue() {
|
||||
|
|
|
@ -106,4 +106,10 @@ public abstract class ValueNode extends DataStructureNode {
|
|||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* The dialogue API is only used for the command line interface.
|
||||
* Therefore, the actual implementation is handled by the command line component.
|
||||
* This API provides a way of creating server-side dialogues which makes
|
||||
* 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()}.
|
||||
* 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)}.
|
||||
|
@ -197,7 +197,8 @@ public abstract class Dialog {
|
|||
if (currentElement == null) {
|
||||
DialogElement next = null;
|
||||
while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
|
||||
};
|
||||
}
|
||||
;
|
||||
return next;
|
||||
}
|
||||
|
||||
|
@ -424,7 +425,7 @@ public abstract class Dialog {
|
|||
public abstract DialogElement start() throws Exception;
|
||||
|
||||
public Dialog evaluateTo(Dialog d) {
|
||||
evaluation = () -> d.evaluation.get();
|
||||
evaluation = () -> d.evaluation != null ? d.evaluation.get() : null;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,11 @@ public class DataSourceId {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -41,12 +41,16 @@ public interface DataStore {
|
|||
|
||||
/**
|
||||
* Creates a display string of this store.
|
||||
* This can be a multiline string.
|
||||
*/
|
||||
default String toDisplay() {
|
||||
default String toSummaryString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String queryInformationString() throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
public static FileStore local(String p) {
|
||||
return new FileStore(MachineFileStore.local(), p);
|
||||
return new FileStore(new LocalStore(), p);
|
||||
}
|
||||
|
||||
MachineFileStore machine;
|
||||
|
@ -38,7 +38,7 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
|||
@Override
|
||||
public void validate() throws Exception {
|
||||
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
|
||||
public String toDisplay() {
|
||||
return file + "@" + machine.toDisplay();
|
||||
public String toSummaryString() {
|
||||
return file + "@" + machine.toSummaryString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,6 +69,10 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
|||
|
||||
@Override
|
||||
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
|
||||
public String toDisplay() {
|
||||
public String toSummaryString() {
|
||||
return "inMemory";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,39 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.core.util.Secret;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@JsonTypeName("local")
|
||||
@Value
|
||||
public class LocalStore implements ShellProcessStore {
|
||||
public class LocalStore extends StandardShellStore implements MachineFileStore {
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static class LocalProcessControl extends ProcessControl {
|
||||
|
||||
private final InputStream input;
|
||||
private final List<Secret> input;
|
||||
private final ProcessBuilder builder;
|
||||
private final Integer timeout;
|
||||
|
||||
private Process process;
|
||||
|
||||
LocalProcessControl(InputStream input, List<String> cmd) {
|
||||
LocalProcessControl(List<Secret> input, List<String> cmd, Integer timeout) {
|
||||
this.input = input;
|
||||
this.timeout = timeout;
|
||||
var l = new ArrayList<String>();
|
||||
l.add("cmd");
|
||||
l.add("/c");
|
||||
|
@ -35,13 +41,19 @@ public class LocalStore implements ShellProcessStore {
|
|||
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
|
||||
public void start() throws IOException {
|
||||
process = builder.start();
|
||||
|
||||
var t = new Thread(() -> {
|
||||
try {
|
||||
input.transferTo(process.getOutputStream());
|
||||
try (var inputStream = createInputStream()){
|
||||
process.getOutputStream().flush();
|
||||
inputStream.transferTo(process.getOutputStream());
|
||||
process.getOutputStream().close();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
|
@ -53,8 +65,12 @@ public class LocalStore implements ShellProcessStore {
|
|||
|
||||
@Override
|
||||
public int waitFor() throws Exception {
|
||||
if (timeout != null) {
|
||||
return process.waitFor(timeout, TimeUnit.SECONDS) ? 0 : -1;
|
||||
} else {
|
||||
return process.waitFor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getStdout() {
|
||||
|
@ -68,7 +84,11 @@ public class LocalStore implements ShellProcessStore {
|
|||
|
||||
@Override
|
||||
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
|
||||
public String toDisplay() {
|
||||
return "local";
|
||||
public String toSummaryString() {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,17 +115,17 @@ public class LocalStore implements ShellProcessStore {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ProcessControl prepareCommand(InputStream input, List<String> cmd) {
|
||||
return new LocalProcessControl(input, cmd);
|
||||
public ProcessControl prepareCommand(List<Secret> input, List<String> cmd, Integer timeout) {
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessControl preparePrivilegedCommand(InputStream input, List<String> cmd) throws Exception {
|
||||
return new LocalProcessControl(input, cmd);
|
||||
public ProcessControl preparePrivilegedCommand(List<Secret> input, List<String> cmd, Integer timeOut) throws Exception {
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellType determineType() {
|
||||
return ShellTypes.CMD;
|
||||
public ShellType determineType() throws Exception {
|
||||
return ShellTypes.determine(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import java.io.OutputStream;
|
|||
|
||||
public interface MachineFileStore extends DataStore {
|
||||
|
||||
static MachineFileStore local() {
|
||||
return new LocalStore();
|
||||
default boolean isLocal(){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
InputStream openInput(String file) throws Exception;
|
||||
|
||||
OutputStream openOutput(String file) throws Exception;
|
||||
|
|
|
@ -33,7 +33,7 @@ public final class NamedStore implements DataStore {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String toDisplay() {
|
||||
public String toSummaryString() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ public abstract class ProcessControl {
|
|||
start();
|
||||
var errT = discardErr();
|
||||
var string = new String(getStdout().readAllBytes(), getCharset());
|
||||
errT.join();
|
||||
waitFor();
|
||||
return string;
|
||||
}
|
||||
|
@ -34,9 +33,6 @@ public abstract class ProcessControl {
|
|||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
outT.join();
|
||||
t.join();
|
||||
|
||||
var ec = waitFor();
|
||||
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;
|
||||
|
||||
import io.xpipe.core.util.Secret;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public interface ShellStore extends MachineFileStore {
|
||||
public abstract class ShellStore implements DataStore {
|
||||
|
||||
static StandardShellStore local() {
|
||||
return new LocalStore();
|
||||
public Integer getTimeout() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String executeAndRead(List<String> cmd) throws Exception {
|
||||
var pc = prepareCommand(InputStream.nullInputStream(), cmd);
|
||||
public List<Secret> getInput() {
|
||||
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.discardErr();
|
||||
var string = new String(pc.getStdout().readAllBytes(), pc.getCharset());
|
||||
return string;
|
||||
}
|
||||
|
||||
default Optional<String> executeAndCheckOut(InputStream in, List<String> cmd) throws Exception {
|
||||
var pc = prepareCommand(in, cmd);
|
||||
public String executeAndCheckOut(List<Secret> in, List<String> cmd, Integer timeout) throws ProcessOutputException, Exception {
|
||||
var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout));
|
||||
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(() -> {
|
||||
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) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
@ -37,15 +63,21 @@ public interface ShellStore extends MachineFileStore {
|
|||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
outT.join();
|
||||
t.join();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
default Optional<String> executeAndCheckErr(InputStream in, List<String> cmd) throws Exception {
|
||||
var pc = prepareCommand(in, cmd);
|
||||
if (ec == 0) {
|
||||
return readOut;
|
||||
} else {
|
||||
throw new ProcessOutputException("Command returned with " + ec + ": " + readError.get());
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> executeAndCheckErr(List<Secret> in, List<String> cmd) throws Exception {
|
||||
var pc = prepareCommand(in, cmd, getTimeout());
|
||||
pc.start();
|
||||
var outT = pc.discardOut();
|
||||
|
||||
|
@ -67,17 +99,27 @@ public interface ShellStore extends MachineFileStore {
|
|||
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
||||
}
|
||||
|
||||
default ProcessControl prepareCommand(List<String> cmd) throws Exception {
|
||||
return prepareCommand(InputStream.nullInputStream(), cmd);
|
||||
public Integer getEffectiveTimeOut(Integer timeout) {
|
||||
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;
|
||||
|
||||
default ProcessControl preparePrivilegedCommand(List<String> cmd) throws Exception {
|
||||
return preparePrivilegedCommand(InputStream.nullInputStream(), cmd);
|
||||
public ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception {
|
||||
return prepareCommand(List.of(), cmd, timeout);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.xpipe.core.util.Secret;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
@ -10,28 +12,28 @@ import java.util.List;
|
|||
public class ShellTypes {
|
||||
|
||||
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
|
||||
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0"));
|
||||
if (o.isPresent() && !o.get().equals("$0")) {
|
||||
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null).strip();
|
||||
if (!o.equals("$0")) {
|
||||
return SH;
|
||||
} else {
|
||||
o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"));
|
||||
if (o.isPresent() && o.get().equals("PowerShell")) {
|
||||
o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null).trim();
|
||||
if (o.equals("PowerShell")) {
|
||||
return POWERSHELL;
|
||||
} else {
|
||||
} else
|
||||
return CMD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
||||
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0"));
|
||||
if (o.isPresent() && !o.get().trim().equals("$0")) {
|
||||
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
|
||||
if (!o.trim().equals("$0")) {
|
||||
return getLinuxShells();
|
||||
} else {
|
||||
return getWindowsShells();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("powershell")
|
||||
public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() {
|
||||
|
||||
@Override
|
||||
|
@ -70,8 +72,14 @@ public class ShellTypes {
|
|||
public String getDisplayName() {
|
||||
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() {
|
||||
|
||||
@Override
|
||||
|
@ -83,9 +91,9 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@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");
|
||||
return st.prepareCommand(InputStream.nullInputStream(), l);
|
||||
return st.prepareCommand(List.of(), l, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,8 +125,14 @@ public class ShellTypes {
|
|||
public String getDisplayName() {
|
||||
return "cmd.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOperatingSystemNameCommand() {
|
||||
return List.of("Get-ComputerInfo");
|
||||
}
|
||||
};
|
||||
|
||||
@JsonProperty("sh")
|
||||
public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() {
|
||||
|
||||
@Override
|
||||
|
@ -127,12 +141,12 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@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);
|
||||
l.add(0, "sudo");
|
||||
l.add(1, "-S");
|
||||
var pws = new ByteArrayInputStream(pw.getBytes(getCharset()));
|
||||
return st.prepareCommand(pws, l);
|
||||
return st.prepareCommand(List.of(Secret.createForSecretValue(pw)), l, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -164,6 +178,11 @@ public class ShellTypes {
|
|||
public String getDisplayName() {
|
||||
return "/bin/sh";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOperatingSystemNameCommand() {
|
||||
return List.of("uname", "-o");
|
||||
}
|
||||
};
|
||||
|
||||
public static StandardShellStore.ShellType getDefault() {
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import io.xpipe.core.util.Secret;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
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);
|
||||
|
||||
default ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception {
|
||||
return st.prepareCommand(in, cmd);
|
||||
default ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||
return st.prepareCommand(in, cmd, timeout);
|
||||
}
|
||||
|
||||
List<String> createFileReadCommand(String file);
|
||||
|
@ -25,7 +29,42 @@ public interface StandardShellStore extends ShellStore {
|
|||
String getName();
|
||||
|
||||
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.dialog;
|
||||
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires static com.fasterxml.jackson.core;
|
||||
requires static com.fasterxml.jackson.databind;
|
||||
requires java.net.http;
|
||||
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'
|
||||
archivesBaseName = 'extension'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'net.synedra:validatorfx:0.3.1'
|
||||
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
||||
implementation project(':core')
|
||||
|
||||
implementation project(':fxcomps')
|
||||
//implementation 'io.xpipe:fxcomps:0.2'
|
||||
// implementation project(':fxcomps')
|
||||
implementation 'io.xpipe:fxcomps:0.2.1'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.1'
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import java.util.Map;
|
|||
|
||||
public interface DataSourceProvider<T extends DataSource<?>> {
|
||||
|
||||
static enum GeneralType {
|
||||
static enum Category {
|
||||
FILE,
|
||||
DATABASE;
|
||||
}
|
||||
|
@ -25,13 +25,13 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
getSourceClass();
|
||||
}
|
||||
|
||||
default GeneralType getGeneralType() {
|
||||
default Category getGeneralType() {
|
||||
if (getFileProvider() != null) {
|
||||
return GeneralType.FILE;
|
||||
return Category.FILE;
|
||||
}
|
||||
|
||||
if (getDatabaseProvider() != null) {
|
||||
return GeneralType.DATABASE;
|
||||
return Category.DATABASE;
|
||||
}
|
||||
|
||||
throw new ExtensionException("Provider has no general type");
|
||||
|
@ -56,7 +56,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
return getId() + "." + key;
|
||||
}
|
||||
|
||||
default Region createConfigGui(Property<T> source) {
|
||||
default Region createConfigGui(Property<T> source, Property<T> appliedSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.MachineFileStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
import io.xpipe.core.store.*;
|
||||
import javafx.beans.property.Property;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -35,9 +32,14 @@ public interface DataStoreProvider {
|
|||
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() {
|
||||
return false;
|
||||
}
|
||||
|
@ -81,7 +83,7 @@ public interface DataStoreProvider {
|
|||
}
|
||||
|
||||
default String display(DataStore store) {
|
||||
return store.toDisplay();
|
||||
return store.toSummaryString();
|
||||
}
|
||||
|
||||
List<String> getPossibleNames();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -53,6 +54,11 @@ public class DataStoreProviders {
|
|||
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")
|
||||
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) {
|
||||
if (ALL == null) {
|
||||
|
|
|
@ -1,28 +1,57 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
public interface SupportedApplicationProvider {
|
||||
|
||||
enum Category {
|
||||
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();
|
||||
|
||||
Supplier<String> getName();
|
||||
ObservableValue<String> getName();
|
||||
|
||||
Category getCategory();
|
||||
|
||||
AccessType getAccessType();
|
||||
|
||||
String getSetupGuideURL();
|
||||
|
||||
default String getGraphicIcon() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
|
|||
|
||||
@Override
|
||||
default Dialog configDialog(T source, boolean all) {
|
||||
return Dialog.empty();
|
||||
return Dialog.empty().evaluateTo(() -> source);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,7 +19,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
|
|||
try {
|
||||
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new AssertionError(e);
|
||||
throw new ExtensionException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import net.synedra.validatorfx.Check;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Validators {
|
||||
|
||||
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.SimpleCompStructure;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
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>> {
|
||||
|
||||
private final Property<Character> value;
|
||||
private final Property<Character> charChoiceValue;
|
||||
private final BidiMap<Character, ObservableValue<String>> range;
|
||||
private final Map<Character, ObservableValue<String>> range;
|
||||
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.range = range;
|
||||
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
|
||||
public CompStructure<HBox> createBase() {
|
||||
var charChoice = new CharComp(value);
|
||||
var rangeCopy = new DualLinkedHashBidiMap<>(range);
|
||||
var rangeCopy = new HashMap<>(range);
|
||||
if (customName != null) {
|
||||
rangeCopy.put(null, customName);
|
||||
}
|
||||
var choice = new ChoiceComp<Character>(charChoiceValue, rangeCopy);
|
||||
var choice = new ChoiceComp<Character>(value, rangeCopy);
|
||||
var charChoiceR = charChoice.createRegion();
|
||||
var choiceR = choice.createRegion();
|
||||
var box = new HBox(charChoiceR, choiceR);
|
||||
|
|
|
@ -7,10 +7,11 @@ import javafx.beans.property.Property;
|
|||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
|
||||
|
||||
|
@ -23,9 +24,10 @@ public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Ch
|
|||
@Override
|
||||
protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
|
||||
var map = new LinkedHashMap<Charset, ObservableValue<String>>();
|
||||
for (var e : Charset.availableCharsets().entrySet()) {
|
||||
map.put(e.getValue(), new SimpleStringProperty(e.getKey()));
|
||||
for (var e : List.of(StandardCharsets.UTF_8, StandardCharsets.UTF_16,
|
||||
StandardCharsets.UTF_16BE, StandardCharsets.ISO_8859_1, Charset.forName("Windows-1251"), Charset.forName("Windows-1252"), StandardCharsets.US_ASCII)) {
|
||||
map.put(e, new SimpleStringProperty(e.displayName()));
|
||||
}
|
||||
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.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.ComboBox;
|
||||
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>>> {
|
||||
|
||||
private final Property<T> value;
|
||||
private final BidiMap<T, ObservableValue<String>> range;
|
||||
Property<T> value;
|
||||
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.range = range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<ComboBox<T>> createBase() {
|
||||
var list = FXCollections.observableArrayList(range.keySet());
|
||||
var cb = new ComboBox<>(list);
|
||||
cb.setConverter(new StringConverter<>() {
|
||||
var cb = new ComboBox<T>();
|
||||
cb.setConverter(new StringConverter<T>() {
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
if (object == null) {
|
||||
return I18n.get("extension.none");
|
||||
}
|
||||
|
||||
return range.get(object).getValue();
|
||||
var found = range.getValue().get(object);
|
||||
if (found == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return found.getValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
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());
|
||||
cb.getStyleClass().add("choice-comp");
|
||||
return new SimpleCompStructure<>(cb);
|
||||
|
|
|
@ -11,7 +11,6 @@ import javafx.beans.value.ObservableValue;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import net.synedra.validatorfx.Check;
|
||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
|
@ -24,6 +23,7 @@ public class DynamicOptionsBuilder<T> {
|
|||
|
||||
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
|
||||
private final List<Property<?>> props = new ArrayList<>();
|
||||
private final List<Property<?>> lazyProperties = new ArrayList<>();
|
||||
|
||||
private final ObservableValue<String> title;
|
||||
private final boolean wrap;
|
||||
|
@ -43,7 +43,15 @@ public class DynamicOptionsBuilder<T> {
|
|||
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) {
|
||||
|
||||
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
|
||||
return this;
|
||||
}
|
||||
|
@ -59,28 +67,42 @@ public class DynamicOptionsBuilder<T> {
|
|||
for (var e : NewLine.values()) {
|
||||
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));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
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));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
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));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
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));
|
||||
props.add(prop);
|
||||
return this;
|
||||
|
@ -107,6 +129,14 @@ public class DynamicOptionsBuilder<T> {
|
|||
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) {
|
||||
var comp = new TextFieldComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
|
@ -120,6 +150,10 @@ public class DynamicOptionsBuilder<T> {
|
|||
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) {
|
||||
var comp = new SecretFieldComp(prop);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
|
@ -134,20 +168,61 @@ public class DynamicOptionsBuilder<T> {
|
|||
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 -> {
|
||||
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) {
|
||||
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(Supplier<V> creator, Property<T> toSet) {
|
||||
return buildComp(creator, toSet).createRegion();
|
||||
public <V extends T> Comp<?> buildBindingComp(Supplier<Property<? extends V>> creator, Property<T> toSet) {
|
||||
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();
|
||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
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) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
|
|
@ -25,7 +25,10 @@ public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
|
|||
|
||||
for (var e : entries) {
|
||||
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.getStyleClass().add("name");
|
||||
ll.setAlignment(Pos.CENTER);
|
||||
|
|
|
@ -5,14 +5,25 @@ import io.xpipe.fxcomps.CompStructure;
|
|||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
private final Property<String> value;
|
||||
private final Property<String> lazyValue;
|
||||
|
||||
public TextFieldComp(Property<String> value) {
|
||||
this.value = value;
|
||||
this.lazyValue = value;
|
||||
|
||||
}
|
||||
|
||||
public TextFieldComp(Property<String> value, Property<String> lazyValue) {
|
||||
this.value = value;
|
||||
this.lazyValue = lazyValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,6 +37,15 @@ public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,22 +9,19 @@ import javafx.beans.value.ObservableValue;
|
|||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
|
||||
|
||||
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.range = range;
|
||||
}
|
||||
|
||||
public BidiMap<T, ObservableValue<String>> getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<HBox> createBase() {
|
||||
var box = new HBox();
|
||||
|
|
|
@ -33,6 +33,11 @@ public class ErrorEvent {
|
|||
.description(msg);
|
||||
}
|
||||
|
||||
public static ErrorEventBuilder fromMessage(String msg) {
|
||||
return builder()
|
||||
.description(msg);
|
||||
}
|
||||
|
||||
public void handle() {
|
||||
EventHandler.get().handle(this);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,45 @@ package io.xpipe.extension.prefs;
|
|||
|
||||
import io.xpipe.extension.I18n;
|
||||
import io.xpipe.extension.Translatable;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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
|
||||
default String toTranslatedString() {
|
||||
return I18n.get(getId());
|
||||
|
|
|
@ -13,7 +13,6 @@ module io.xpipe.extension {
|
|||
requires javafx.graphics;
|
||||
requires transitive javafx.controls;
|
||||
requires io.xpipe.fxcomps;
|
||||
requires static org.apache.commons.collections4;
|
||||
requires static lombok;
|
||||
requires static com.dlsc.preferencesfx;
|
||||
requires static com.dlsc.formsfx;
|
||||
|
|
|
@ -5,3 +5,8 @@ lf=LF (Linux)
|
|||
none=None
|
||||
nullPointer=Null Pointer: $MSG$
|
||||
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 {
|
||||
environment {
|
||||
properties.put('rawChangelog', file("misc/changelogs/${canonicalVersion}.txt").exists() ?
|
||||
file("misc/changelogs/${canonicalVersion}.txt").text.replace('\r\n', '\n') : "")
|
||||
properties.put('rawChangelog', file("misc/changelogs/${version}.txt").exists() ?
|
||||
file("misc/changelogs/${version}.txt").text.replace('\r\n', '\n') : "")
|
||||
}
|
||||
|
||||
project {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
0.1
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
🚀 {{projectName}} {{projectVersion}} has been released: {{releaseNotesUrl}}
|
||||
|
||||
If you have already installed {{projectName}} and have updates enabled,
|
||||
this update will be automatically installed when launching it the next time.
|
||||
|
||||
You can get the standalone version here: {{releaseNotesUrl}}
|
||||
The documentation and maven repositories should be automatically updated within the next couple of hours.
|
||||
|
||||
Changes in {{projectVersion}}:
|
||||
{{{rawChangelog}}}
|
|
@ -5,8 +5,7 @@
|
|||
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.
|
||||
|
||||
Also note that pre-releases are not downloaded by the automatic updater
|
||||
until they are ready for a full release or you have opted in to receiving pre-releases.
|
||||
The documentation and maven repositories should be automatically updated within the next couple of hours.
|
||||
|
||||
Changes in {{projectVersion}}:
|
||||
{{{rawChangelog}}}
|
|
@ -1 +1 @@
|
|||
0.1-SNAPSHOT
|
||||
0.0.1-SNAPSHOT
|
|
@ -4,3 +4,12 @@ include 'api'
|
|||
include 'core'
|
||||
include 'beacon'
|
||||
include 'extension'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue