This commit is contained in:
Christopher Schnick 2022-10-11 12:23:37 +02:00
parent f12ce82933
commit 8d95f451bb
40 changed files with 471 additions and 191 deletions

View file

@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.node.TextNode;
import io.xpipe.beacon.exchange.MessageExchanges; import io.xpipe.beacon.exchange.MessageExchanges;
import io.xpipe.beacon.exchange.data.ClientErrorMessage; import io.xpipe.beacon.exchange.data.ClientErrorMessage;
import io.xpipe.beacon.exchange.data.ServerErrorMessage; import io.xpipe.beacon.exchange.data.ServerErrorMessage;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonMapper;
import java.io.*; import java.io.*;
import java.net.InetAddress; import java.net.InetAddress;
@ -96,7 +96,7 @@ public class BeaconClient implements AutoCloseable {
} }
public <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException { public <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
ObjectNode json = JacksonHelper.newMapper().valueToTree(req); ObjectNode json = JacksonMapper.newMapper().valueToTree(req);
var prov = MessageExchanges.byRequest(req); var prov = MessageExchanges.byRequest(req);
if (prov.isEmpty()) { if (prov.isEmpty()) {
throw new ClientException("Unknown request class " + req.getClass()); throw new ClientException("Unknown request class " + req.getClass());
@ -113,7 +113,7 @@ public class BeaconClient implements AutoCloseable {
} }
var writer = new StringWriter(); var writer = new StringWriter();
var mapper = JacksonHelper.newMapper(); var mapper = JacksonMapper.newMapper();
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) { try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
g.writeTree(msg); g.writeTree(msg);
} catch (IOException ex) { } catch (IOException ex) {
@ -136,7 +136,7 @@ public class BeaconClient implements AutoCloseable {
public <T extends ResponseMessage> T receiveResponse() throws ConnectorException, ClientException, ServerException { public <T extends ResponseMessage> T receiveResponse() throws ConnectorException, ClientException, ServerException {
JsonNode node; JsonNode node;
try (InputStream blockIn = BeaconFormat.readBlocks(in)) { try (InputStream blockIn = BeaconFormat.readBlocks(in)) {
node = JacksonHelper.newMapper().readTree(blockIn); node = JacksonMapper.newMapper().readTree(blockIn);
} catch (SocketException ex) { } catch (SocketException ex) {
throw new ConnectorException("Connection to xpipe daemon closed unexpectedly", ex); throw new ConnectorException("Connection to xpipe daemon closed unexpectedly", ex);
} catch (IOException ex) { } catch (IOException ex) {
@ -172,7 +172,7 @@ public class BeaconClient implements AutoCloseable {
} }
try { try {
var reader = JacksonHelper.newMapper().readerFor(ClientErrorMessage.class); var reader = JacksonMapper.newMapper().readerFor(ClientErrorMessage.class);
return Optional.of(reader.readValue(content)); return Optional.of(reader.readValue(content));
} catch (IOException ex) { } catch (IOException ex) {
throw new ConnectorException("Couldn't parse client error message", ex); throw new ConnectorException("Couldn't parse client error message", ex);
@ -186,7 +186,7 @@ public class BeaconClient implements AutoCloseable {
} }
try { try {
var reader = JacksonHelper.newMapper().readerFor(ServerErrorMessage.class); var reader = JacksonMapper.newMapper().readerFor(ServerErrorMessage.class);
return Optional.of(reader.readValue(content)); return Optional.of(reader.readValue(content));
} catch (IOException ex) { } catch (IOException ex) {
throw new ConnectorException("Couldn't parse server error message", ex); throw new ConnectorException("Couldn't parse server error message", ex);
@ -212,7 +212,7 @@ public class BeaconClient implements AutoCloseable {
} }
try { try {
var reader = JacksonHelper.newMapper().readerFor(prov.get().getResponseClass()); var reader = JacksonMapper.newMapper().readerFor(prov.get().getResponseClass());
return reader.readValue(content); return reader.readValue(content);
} catch (IOException ex) { } catch (IOException ex) {
throw new ConnectorException("Couldn't parse response", ex); throw new ConnectorException("Couldn't parse response", ex);

View file

@ -33,8 +33,10 @@ public abstract class ArrayNode extends DataStructureNode {
} }
var toReturn = getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes()); var toReturn = getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
// Useful for debugging
if (toReturn == false) { if (toReturn == false) {
throw new AssertionError(); return false;
} }
return toReturn; return toReturn;
} }

View file

@ -4,11 +4,11 @@ import lombok.NonNull;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public class SimpleImmutableValueNode extends ValueNode { public class SimpleValueNode extends ValueNode {
private final byte @NonNull [] data; private final byte @NonNull [] data;
SimpleImmutableValueNode(byte @NonNull [] data) { SimpleValueNode(byte @NonNull [] data) {
this.data = data; this.data = data;
} }
@ -18,10 +18,6 @@ public class SimpleImmutableValueNode extends ValueNode {
@Override @Override
public final String asString() { public final String asString() {
if (getRawData().length == 0 && !hasMetaAttribute(IS_TEXT)) {
return "null";
}
return new String(getRawData(), StandardCharsets.UTF_8); return new String(getRawData(), StandardCharsets.UTF_8);
} }

View file

@ -59,11 +59,18 @@ public abstract class TupleNode extends DataStructureNode {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) {
if (!(o instanceof TupleNode that)) return false; return true;
var toReturn = getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes()); }
if (!(o instanceof TupleNode that)) {
return false;
}
var toReturn = getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()) &&
Objects.equals(getMetaAttributes(), that.getMetaAttributes());
// Useful for debugging
if (toReturn == false) { if (toReturn == false) {
throw new AssertionError(); return false;
} }
return toReturn; return toReturn;
} }
@ -93,7 +100,8 @@ public abstract class TupleNode extends DataStructureNode {
boolean hasKeys = entries.stream().anyMatch(kv -> kv.key() != null); boolean hasKeys = entries.stream().anyMatch(kv -> kv.key() != null);
return hasKeys ? TupleNode.of( return hasKeys ? TupleNode.of(
entries.stream().map(KeyValue::key).toList(), entries.stream().map(KeyValue::key).toList(),
entries.stream().map(KeyValue::value).toList()) : entries.stream().map(KeyValue::value).toList()
) :
TupleNode.of(entries.stream().map(KeyValue::value).toList()); TupleNode.of(entries.stream().map(KeyValue::value).toList());
} }
} }

View file

@ -24,9 +24,12 @@ public abstract class ValueNode extends DataStructureNode {
return false; return false;
} }
var toReturn = Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes()); var toReturn = Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
// Useful for debugging
if (toReturn == false) { if (toReturn == false) {
throw new AssertionError(); return false;
} }
return toReturn; return toReturn;
} }
@ -36,7 +39,7 @@ public abstract class ValueNode extends DataStructureNode {
} }
public static ValueNode nullValue() { public static ValueNode nullValue() {
return new SimpleImmutableValueNode(new byte[0]).tag(IS_NULL).asValue(); return new SimpleValueNode(new byte[0]).tag(IS_NULL).asValue();
} }
public static ValueNode of(byte[] data) { public static ValueNode of(byte[] data) {
@ -44,7 +47,7 @@ public abstract class ValueNode extends DataStructureNode {
return nullValue(); return nullValue();
} }
return new SimpleImmutableValueNode(data); return new SimpleValueNode(data);
} }
public static ValueNode ofBytes(byte[] data) { public static ValueNode ofBytes(byte[] data) {

View file

@ -11,16 +11,19 @@ import java.util.List;
public abstract class BatchTableWriteConnection implements TableWriteConnection { public abstract class BatchTableWriteConnection implements TableWriteConnection {
public static final int BATCH_SIZE = 2000; public static final int DEFAULT_BATCH_SIZE = 2000;
protected final int batchSize = DEFAULT_BATCH_SIZE;
private final List<DataStructureNode> batch = new ArrayList<>(); private final List<DataStructureNode> batch = new ArrayList<>();
@Override @Override
public final DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor() { public final DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor() {
return node -> { return node -> {
if (batch.size() < BATCH_SIZE) { if (batch.size() < batchSize) {
batch.add(node); batch.add(node);
return true; if (batch.size() < batchSize) {
return true;
}
} }
var array = ArrayNode.of(batch); var array = ArrayNode.of(batch);
@ -32,12 +35,15 @@ public abstract class BatchTableWriteConnection implements TableWriteConnection
@Override @Override
public final void close() throws Exception { public final void close() throws Exception {
if (batch.size() > 0) { try {
var array = ArrayNode.of(batch); if (batch.size() > 0) {
var returned = writeBatchLinesAcceptor().accept(array); var array = ArrayNode.of(batch);
batch.clear(); var returned = writeBatchLinesAcceptor().accept(array);
batch.clear();
}
} finally {
onClose();
} }
onClose();
} }
protected abstract void onClose() throws Exception; protected abstract void onClose() throws Exception;

View file

@ -1,6 +1,5 @@
package io.xpipe.core.impl; package io.xpipe.core.impl;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import io.xpipe.core.data.node.DataStructureNodeAcceptor; import io.xpipe.core.data.node.DataStructureNodeAcceptor;
@ -10,7 +9,7 @@ import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader; import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.source.TableReadConnection; import io.xpipe.core.source.TableReadConnection;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonMapper;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
@ -35,7 +34,7 @@ public class XpbtReadConnection implements TableReadConnection {
var headerLength = header.getBytes(StandardCharsets.UTF_8).length; var headerLength = header.getBytes(StandardCharsets.UTF_8).length;
this.inputStream.skip(headerLength + 1); this.inputStream.skip(headerLength + 1);
List<String> names = JacksonHelper.newMapper() List<String> names = JacksonMapper.newMapper()
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE) .disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
.readerFor(new TypeReference<List<String>>() { .readerFor(new TypeReference<List<String>>() {
}).readValue(header); }).readValue(header);

View file

@ -9,7 +9,7 @@ import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.typed.TypedDataStreamWriter; import io.xpipe.core.data.typed.TypedDataStreamWriter;
import io.xpipe.core.source.TableWriteConnection; import io.xpipe.core.source.TableWriteConnection;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonMapper;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -58,7 +58,7 @@ public class XpbtWriteConnection implements TableWriteConnection {
try (JsonGenerator g = f.createGenerator(writer) try (JsonGenerator g = f.createGenerator(writer)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
.setPrettyPrinter(new DefaultPrettyPrinter())) { .setPrettyPrinter(new DefaultPrettyPrinter())) {
JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) JacksonMapper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
.writeValue(g, tupleNode.getKeyNames()); .writeValue(g, tupleNode.getKeyNames());
writer.append("\n"); writer.append("\n");
} }

View file

@ -8,7 +8,8 @@ import io.xpipe.core.impl.TextSource;
import io.xpipe.core.impl.XpbtSource; import io.xpipe.core.impl.XpbtSource;
import io.xpipe.core.store.DataFlow; import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.JacksonizedValue;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
@ -25,7 +26,7 @@ import java.util.Optional;
use = JsonTypeInfo.Id.NAME, use = JsonTypeInfo.Id.NAME,
property = "type" property = "type"
) )
public abstract class DataSource<DS extends DataStore> { public abstract class DataSource<DS extends DataStore> extends JacksonizedValue {
public static DataSource<?> createInternalDataSource(DataSourceType t, DataStore store) { public static DataSource<?> createInternalDataSource(DataSourceType t, DataStore store) {
@ -48,7 +49,7 @@ public abstract class DataSource<DS extends DataStore> {
public void test() throws Exception { public void test() throws Exception {
store.test(); store.validate();
} }
public void validate() throws Exception { public void validate() throws Exception {
@ -56,7 +57,7 @@ public abstract class DataSource<DS extends DataStore> {
throw new IllegalStateException("Store cannot be null"); throw new IllegalStateException("Store cannot be null");
} }
store.validate(); store.checkComplete();
} }
public WriteMode[] getAvailableWriteModes() { public WriteMode[] getAvailableWriteModes() {
@ -79,38 +80,12 @@ public abstract class DataSource<DS extends DataStore> {
@SneakyThrows @SneakyThrows
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends DataSource<DS>> T copy() { public <T extends DataSource<DS>> T copy() {
var mapper = JacksonHelper.newMapper(); var mapper = JacksonMapper.newMapper();
TokenBuffer tb = new TokenBuffer(mapper, false); TokenBuffer tb = new TokenBuffer(mapper, false);
mapper.writeValue(tb, this); mapper.writeValue(tb, this);
return (T) mapper.readValue(tb.asParser(), getClass()); return (T) mapper.readValue(tb.asParser(), getClass());
} }
@SneakyThrows
public final String toString() {
var tree = JacksonHelper.newMapper().valueToTree(this);
return tree.toPrettyString();
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (getClass() != o.getClass()) {
return false;
}
var tree = JacksonHelper.newMapper().valueToTree(this);
var otherTree = JacksonHelper.newMapper().valueToTree(o);
return tree.equals(otherTree);
}
@Override
public final int hashCode() {
var tree = JacksonHelper.newMapper().valueToTree(this);
return tree.hashCode();
}
public DataSource<DS> withStore(DS store) { public DataSource<DS> withStore(DS store) {
var c = copy(); var c = copy();
c.store = store; c.store = store;

View file

@ -21,6 +21,12 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
} }
public final TableReadConnection openReadConnection() throws Exception { public final TableReadConnection openReadConnection() throws Exception {
try {
validate();
} catch (Exception e) {
return TableReadConnection.empty();
}
var con = newReadConnection(); var con = newReadConnection();
con.init(); con.init();
return con; return con;

View file

@ -1,6 +1,6 @@
package io.xpipe.core.store; package io.xpipe.core.store;
public abstract class CollectionEntryDataStore implements StreamDataStore, FilenameStore { public abstract class CollectionEntryDataStore implements FilenameStore, StreamDataStore {
private final boolean directory; private final boolean directory;
private final String name; private final String name;

View file

@ -56,10 +56,10 @@ public interface DataStore {
* *
* @throws Exception if any part of the validation went wrong * @throws Exception if any part of the validation went wrong
*/ */
default void test() throws Exception { default void validate() throws Exception {
} }
default void validate() throws Exception { default void checkComplete() throws Exception {
} }
default boolean delete() throws Exception { default boolean delete() throws Exception {

View file

@ -1,8 +1,9 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Builder; import io.xpipe.core.util.JacksonizedValue;
import lombok.Value; import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.io.InputStream; import java.io.InputStream;
@ -12,11 +13,11 @@ import java.nio.file.Path;
/** /**
* Represents a file located on a certain machine. * Represents a file located on a certain machine.
*/ */
@Value
@JsonTypeName("file") @JsonTypeName("file")
@Builder @SuperBuilder
@Jacksonized @Jacksonized
public class FileStore implements StreamDataStore, FilenameStore { @Getter
public class FileStore extends JacksonizedValue implements FilenameStore, StreamDataStore {
public static FileStore local(Path p) { public static FileStore local(Path p) {
return new FileStore(new LocalStore(), p.toString()); return new FileStore(new LocalStore(), p.toString());
@ -32,8 +33,13 @@ public class FileStore implements StreamDataStore, FilenameStore {
MachineFileStore machine; MachineFileStore machine;
String file; String file;
public FileStore(MachineFileStore machine, String file) {
this.machine = machine;
this.file = file;
}
@Override @Override
public void validate() throws Exception { public void checkComplete() throws Exception {
if (machine == null) { if (machine == null) {
throw new IllegalStateException("Machine is missing"); throw new IllegalStateException("Machine is missing");
} }

View file

@ -2,25 +2,24 @@ package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value; import io.xpipe.core.util.JacksonizedValue;
import lombok.experimental.NonFinal; import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets;
/** /**
* A store whose contents are stored in memory. * A store whose contents are stored in memory.
*/ */
@Value
@JsonTypeName("inMemory") @JsonTypeName("inMemory")
public class InMemoryStore implements StreamDataStore { @SuperBuilder
@Jacksonized
@Getter
public class InMemoryStore extends JacksonizedValue implements StreamDataStore {
@NonFinal private byte[] value;
byte[] value;
public InMemoryStore() {
this.value = new byte[0];
}
@JsonCreator @JsonCreator
public InMemoryStore(byte[] value) { public InMemoryStore(byte[] value) {
@ -47,8 +46,4 @@ public class InMemoryStore implements StreamDataStore {
} }
}; };
} }
public String toString() {
return new String(value, StandardCharsets.UTF_8);
}
} }

View file

@ -2,9 +2,8 @@ package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import lombok.EqualsAndHashCode;
import lombok.Value;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -16,9 +15,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@JsonTypeName("local") @JsonTypeName("local")
@Value public class LocalStore extends JacksonizedValue implements MachineFileStore, StandardShellStore {
@EqualsAndHashCode(callSuper = false)
public class LocalStore extends StandardShellStore implements MachineFileStore {
@Override @Override
public boolean isLocal() { public boolean isLocal() {

View file

@ -7,11 +7,6 @@ import java.io.OutputStream;
public interface MachineFileStore extends DataStore { public interface MachineFileStore extends DataStore {
default boolean isLocal(){
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;

View file

@ -23,7 +23,7 @@ public final class NamedStore implements DataStore {
} }
@Override @Override
public void test() throws Exception { public void validate() throws Exception {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -15,7 +15,7 @@ public abstract class ProcessControl {
var errT = discardErr(); var errT = discardErr();
var string = new String(getStdout().readAllBytes(), getCharset()); var string = new String(getStdout().readAllBytes(), getCharset());
waitFor(); waitFor();
return string; return string.trim();
} }
public Optional<String> readErrOnly() throws Exception { public Optional<String> readErrOnly() throws Exception {
@ -34,7 +34,7 @@ public abstract class ProcessControl {
t.start(); t.start();
var ec = waitFor(); var ec = waitFor();
return ec != 0 ? Optional.of(read.get()) : Optional.empty(); return ec != 0 ? Optional.of(read.get().trim()) : Optional.empty();
} }
public Thread discardOut() { public Thread discardOut() {

View file

@ -2,28 +2,23 @@ package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
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 abstract class ShellStore implements DataStore { public interface ShellStore extends DataStore {
public Integer getTimeout() { public default Integer getTimeout() {
return null; return null;
} }
public List<SecretValue> getInput() { public default List<SecretValue> getInput() {
return List.of(); return List.of();
} }
public boolean isLocal() { public default String executeAndRead(List<String> cmd, Integer timeout) throws Exception {
return false;
}
public String executeAndRead(List<String> cmd, Integer timeout) throws Exception {
var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout)); var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout));
pc.start(); pc.start();
pc.discardErr(); pc.discardErr();
@ -31,7 +26,8 @@ public abstract class ShellStore implements DataStore {
return string; return string;
} }
public String executeAndCheckOut(List<SecretValue> in, List<String> cmd, Integer timeout) throws ProcessOutputException, Exception { public default String executeAndCheckOut(List<SecretValue> in, List<String> cmd, Integer timeout)
throws ProcessOutputException, Exception {
var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout)); var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout));
pc.start(); pc.start();
@ -47,15 +43,10 @@ public abstract class ShellStore implements DataStore {
errorThread.setDaemon(true); errorThread.setDaemon(true);
errorThread.start(); errorThread.start();
var read = new ByteArrayOutputStream(); AtomicReference<String> read = new AtomicReference<>();
var t = new Thread(() -> { var t = new Thread(() -> {
try { try {
final byte[] buf = new byte[1]; read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
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);
} }
@ -64,19 +55,18 @@ public abstract class ShellStore implements DataStore {
t.start(); t.start();
var ec = pc.waitFor(); var ec = pc.waitFor();
var readOut = read.toString(pc.getCharset());
if (ec == -1) { if (ec == -1) {
throw new ProcessOutputException("Command timed out"); throw new ProcessOutputException("Command timed out");
} }
if (ec == 0) { if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
return readOut; return read.get().trim();
} else { } else {
throw new ProcessOutputException("Command returned with " + ec + ": " + readError.get()); throw new ProcessOutputException("Command returned with " + ec + ": " + readError.get().trim());
} }
} }
public Optional<String> executeAndCheckErr(List<SecretValue> in, List<String> cmd) throws Exception { public default Optional<String> executeAndCheckErr(List<SecretValue> in, List<String> cmd) throws Exception {
var pc = prepareCommand(in, cmd, getTimeout()); var pc = prepareCommand(in, cmd, getTimeout());
pc.start(); pc.start();
var outT = pc.discardOut(); var outT = pc.discardOut();
@ -99,7 +89,7 @@ public abstract class ShellStore implements DataStore {
return ec != 0 ? Optional.of(read.get()) : Optional.empty(); return ec != 0 ? Optional.of(read.get()) : Optional.empty();
} }
public Integer getEffectiveTimeOut(Integer timeout) { public default Integer getEffectiveTimeOut(Integer timeout) {
if (this.getTimeout() == null) { if (this.getTimeout() == null) {
return timeout; return timeout;
} }
@ -109,17 +99,19 @@ public abstract class ShellStore implements DataStore {
return Math.min(getTimeout(), timeout); return Math.min(getTimeout(), timeout);
} }
public ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception { public default ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception {
return prepareCommand(List.of(), cmd, timeout); return prepareCommand(List.of(), cmd, timeout);
} }
public abstract ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout) throws Exception; public abstract ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout)
throws Exception;
public ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout) throws Exception { public default ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout) throws Exception {
return preparePrivilegedCommand(List.of(), cmd, timeout); return preparePrivilegedCommand(List.of(), cmd, timeout);
} }
public ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeout) throws Exception { public default ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeout)
throws Exception {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View file

@ -77,8 +77,10 @@ public class ShellTypes {
var l = new ArrayList<>(cmd); var l = new ArrayList<>(cmd);
l.add(0, "cmd.exe"); l.add(0, "cmd.exe");
l.add(1, "/c"); l.add(1, "/c");
l.add(2, "@chcp 65001>nul"); l.add(2, "@chcp 65001");
l.add(3, "&&"); l.add(3, ">");
l.add(4, "nul");
l.add(5, "&&");
return l; return l;
} }

View file

@ -9,7 +9,11 @@ import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
public abstract class StandardShellStore extends ShellStore implements MachineFileStore { public interface StandardShellStore extends MachineFileStore, ShellStore {
public default boolean isLocal() {
return false;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public static interface ShellType { public static interface ShellType {
@ -38,19 +42,19 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
} }
public NewLine getNewLine() throws Exception { public default NewLine getNewLine() throws Exception {
return determineType().getNewLine(); return determineType().getNewLine();
} }
public abstract ShellType determineType() throws Exception; public abstract ShellType determineType() throws Exception;
public final String querySystemName() throws Exception { public default String querySystemName() throws Exception {
var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout()); var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout());
return result.strip(); return result.strip();
} }
@Override @Override
public InputStream openInput(String file) throws Exception { public default InputStream openInput(String file) throws Exception {
var type = determineType(); var type = determineType();
var cmd = type.createFileReadCommand(file); var cmd = type.createFileReadCommand(file);
var p = prepareCommand(List.of(), cmd, null); var p = prepareCommand(List.of(), cmd, null);
@ -59,7 +63,7 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
} }
@Override @Override
public OutputStream openOutput(String file) throws Exception { public default OutputStream openOutput(String file) throws Exception {
return null; return null;
// var type = determineType(); // var type = determineType();
// var cmd = type.createFileWriteCommand(file); // var cmd = type.createFileWriteCommand(file);
@ -69,7 +73,8 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
} }
@Override @Override
public boolean exists(String file) throws Exception {
public default boolean exists(String file) throws Exception {
var type = determineType(); var type = determineType();
var cmd = type.createFileExistsCommand(file); var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(List.of(), cmd, null); var p = prepareCommand(List.of(), cmd, null);
@ -78,7 +83,7 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
} }
@Override @Override
public void mkdirs(String file) throws Exception { public default void mkdirs(String file) throws Exception {
} }
} }

View file

@ -1,17 +1,16 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode; import io.xpipe.core.util.JacksonizedValue;
import lombok.Value; import lombok.experimental.SuperBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@EqualsAndHashCode
@Value
@JsonTypeName("stdin") @JsonTypeName("stdin")
public class StdinDataStore implements StreamDataStore { @SuperBuilder
public class StdinDataStore extends JacksonizedValue implements StreamDataStore {
@Override @Override
public boolean isContentExclusivelyAccessible() { public boolean isContentExclusivelyAccessible() {

View file

@ -1,16 +1,15 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode; import io.xpipe.core.util.JacksonizedValue;
import lombok.Value; import lombok.experimental.SuperBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@EqualsAndHashCode
@Value
@JsonTypeName("stdout") @JsonTypeName("stdout")
public class StdoutDataStore implements StreamDataStore { @SuperBuilder
public class StdoutDataStore extends JacksonizedValue implements StreamDataStore {
@Override @Override
public boolean canOpen() throws Exception { public boolean canOpen() throws Exception {

View file

@ -10,35 +10,40 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
public class JacksonHelper { public class JacksonMapper {
private static final ObjectMapper BASE = new ObjectMapper(); private static final ObjectMapper BASE = new ObjectMapper();
private static ObjectMapper INSTANCE = new ObjectMapper(); private static ObjectMapper INSTANCE = new ObjectMapper();
private static final ObjectMapper DEFAULT;
private static boolean init = false; private static boolean init = false;
private static List<Module> MODULES;
public static synchronized void initClassBased() {
initModularized(null);
}
public static synchronized void initModularized(ModuleLayer layer) {
MODULES = findModules(layer);
static {
ObjectMapper objectMapper = BASE; ObjectMapper objectMapper = BASE;
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker() objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE) .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE) .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE) .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)); .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
var modules = findModules(ModuleLayer.boot());
objectMapper.registerModules(modules);
INSTANCE = BASE.copy(); INSTANCE = BASE.copy();
INSTANCE.registerModules(MODULES); DEFAULT = BASE.copy();
}
public static synchronized void initClassBased() {
initModularized(null);
}
public static synchronized void initModularized(ModuleLayer layer) {
List<Module> MODULES = findModules(layer);
INSTANCE.registerModules(MODULES);
init = true; init = true;
} }
@ -56,8 +61,8 @@ public class JacksonHelper {
* Constructs a new ObjectMapper that is able to map all required X-Pipe classes and also possible extensions. * Constructs a new ObjectMapper that is able to map all required X-Pipe classes and also possible extensions.
*/ */
public static ObjectMapper newMapper() { public static ObjectMapper newMapper() {
if (!JacksonHelper.isInit()) { if (!JacksonMapper.isInit()) {
JacksonHelper.initModularized(ModuleLayer.boot()); return DEFAULT;
} }
return INSTANCE.copy(); return INSTANCE.copy();
} }

View file

@ -0,0 +1,38 @@
package io.xpipe.core.util;
import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder;
@SuperBuilder
public class JacksonizedValue {
public JacksonizedValue() {
}
@SneakyThrows
public final String toString() {
var tree = JacksonMapper.newMapper().valueToTree(this);
return tree.toPrettyString();
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass() != o.getClass()) {
return false;
}
var tree = JacksonMapper.newMapper().valueToTree(this);
var otherTree = JacksonMapper.newMapper().valueToTree(o);
return tree.equals(otherTree);
}
@Override
public final int hashCode() {
var tree = JacksonMapper.newMapper().valueToTree(this);
return tree.hashCode();
}
}

View file

@ -36,7 +36,10 @@ public class SecretValue {
if (value.length() < 2) { if (value.length() < 2) {
return value; return value;
} }
try {
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
} catch (Exception exception) {
return "";
}
} }
} }

View file

@ -61,10 +61,17 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return i18n("displayDescription"); return i18n("displayDescription");
} }
default String getDisplayIconFileName() { default String getModuleName() {
return getId() + ":icon.png"; var n = getClass().getPackageName();
var i = n.lastIndexOf('.');
return i != -1 ? n.substring(i + 1) : n;
} }
default String getDisplayIconFileName() {
return getModuleName() + ":" + getId() + "_icon.png";
}
default String getSourceDescription(T source) { default String getSourceDescription(T source) {
return getDisplayName(); return getDisplayName();
} }

View file

@ -2,6 +2,7 @@ package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.*; import io.xpipe.core.store.*;
import io.xpipe.core.util.JacksonizedValue;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import java.util.List; import java.util.List;
@ -14,6 +15,16 @@ public interface DataStoreProvider {
DATABASE; DATABASE;
} }
default void validate() throws Exception {
getCategory();
for (Class<?> storeClass : getStoreClasses()) {
if (!JacksonizedValue.class.isAssignableFrom(storeClass)) {
throw new ExtensionException(String.format("Store class %s is not a Jacksonized value", storeClass.getSimpleName()));
}
}
}
default Category getCategory() { default Category getCategory() {
var c = getStoreClasses().get(0); var c = getStoreClasses().get(0);
if (StreamDataStore.class.isAssignableFrom(c)) { if (StreamDataStore.class.isAssignableFrom(c)) {

View file

@ -19,9 +19,12 @@ public class DataStoreProviders {
.collect(Collectors.toList()); .collect(Collectors.toList());
ALL.removeIf(p -> { ALL.removeIf(p -> {
try { try {
return !p.init(); p.init();
p.validate();
return false;
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).handle(); ErrorEvent.fromThrowable(e)
.handle();
return true; return true;
} }
}); });
@ -56,17 +59,17 @@ public class DataStoreProviders {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends DataStoreProvider> T byStore(DataStore store) { public static <T extends DataStoreProvider> T byStore(DataStore store) {
return (T) byStoreClass(store.getClass()); return (T) byStoreClass(store.getClass()).orElseThrow();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) { public static <T extends DataStoreProvider> Optional<T> byStoreClass(Class<?> c) {
if (ALL == null) { if (ALL == null) {
throw new IllegalStateException("Not initialized"); throw new IllegalStateException("Not initialized");
} }
return (T) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny().orElseThrow(); return (Optional<T>) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny();
} }
public static List<DataStoreProvider> getAll() { public static List<DataStoreProvider> getAll() {

View file

@ -80,7 +80,10 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
public void hide() { public void hide() {
Window owner = getOwnerWindow(); Window owner = getOwnerWindow();
if (owner == null || owner.isFocused()) { if (owner == null || owner.isFocused()) {
super.hide(); try {
super.hide();
} catch (Exception e) {
}
} }
} }

View file

@ -47,8 +47,6 @@ public class FilterComp extends Comp<FilterComp.Structure> {
var stack = new StackPane(bgLabel, filter); var stack = new StackPane(bgLabel, filter);
stack.getStyleClass().add("filter-comp"); stack.getStyleClass().add("filter-comp");
bgLabel.prefHeightProperty().bind(stack.heightProperty());
filter.prefHeightProperty().bind(stack.heightProperty());
return Structure.builder().inactiveIcon(fi).inactiveText(bgLabel).text(filter).pane(stack).build(); return Structure.builder().inactiveIcon(fi).inactiveText(bgLabel).text(filter).pane(stack).build();
} }

View file

@ -0,0 +1,124 @@
package io.xpipe.extension.comp;
import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.util.PlatformThread;
import io.xpipe.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebView;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.Value;
import java.util.Set;
import java.util.regex.Pattern;
@Getter
public class SvgComp {
private static Size parseSize(String string) {
for (SizeUnits unit : SizeUnits.values()) {
if (string.endsWith(unit.toString())) {
return new Size(
Double.parseDouble(string.substring(
0, string.length() - unit.toString().length())),
unit);
}
}
return new Size(Double.parseDouble(string), SizeUnits.PX);
}
@SneakyThrows
public static SvgComp create(ObservableValue<String> content) {
var widthProperty = new SimpleIntegerProperty();
var heightProperty = new SimpleIntegerProperty();
SimpleChangeListener.apply(content, val -> {
var regularExpression = Pattern.compile("<svg.+?width=\"([^\s]+)\"", Pattern.DOTALL);
var matcher = regularExpression.matcher(val);
matcher.find();
var width = matcher.group(1);
regularExpression = Pattern.compile("<svg.+?height=\"([^\s]+)\"", Pattern.DOTALL);
matcher = regularExpression.matcher(val);
matcher.find();
var height = matcher.group(1);
var widthInteger = parseSize(width).pixels();
var heightInteger = parseSize(height).pixels();
widthProperty.set((int) Math.ceil(widthInteger));
heightProperty.set((int) Math.ceil(heightInteger));
});
return new SvgComp(widthProperty, heightProperty, content);
}
@Value
@Builder
public static class Structure implements CompStructure<StackPane> {
StackPane pane;
WebView webView;
@Override
public StackPane get() {
return pane;
}
}
private final ObservableValue<Number> width;
private final ObservableValue<Number> height;
private final ObservableValue<String> svgContent;
public SvgComp(ObservableValue<Number> width, ObservableValue<Number> height, ObservableValue<String> svgContent) {
this.width = PlatformThread.sync(width);
this.height = PlatformThread.sync(height);
this.svgContent = PlatformThread.sync(svgContent);
}
private String getHtml(String content) {
return "<html><body style='margin: 0; padding: 0; border: none;' >" + content + "</body></html>";
}
private WebView createWebView() {
var wv = new WebView();
wv.setPageFill(Color.TRANSPARENT);
wv.setDisable(true);
wv.getEngine().loadContent(getHtml(svgContent.getValue()));
svgContent.addListener((c, o, n) -> {
wv.getEngine().loadContent(getHtml(n));
});
// Hide scrollbars that popup on every content change. Bug in WebView?
wv.getChildrenUnmodifiable().addListener((ListChangeListener<Node>) change -> {
Set<Node> scrolls = wv.lookupAll(".scroll-bar");
for (Node scroll : scrolls) {
scroll.setVisible(false);
}
});
// As the aspect ratio of the WebView is kept constant, we can compute the zoom only using the width
wv.zoomProperty()
.bind(Bindings.createDoubleBinding(
() -> {
return wv.getWidth() / width.getValue().doubleValue();
},
wv.widthProperty(),
width));
wv.maxWidthProperty().bind(wv.prefWidthProperty());
wv.maxHeightProperty().bind(wv.prefHeightProperty());
return wv;
}
public WebView createWebview() {
var wv = createWebView();
wv.getStyleClass().add("svg-comp");
return wv;
}
}

View file

@ -14,6 +14,7 @@ import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import net.synedra.validatorfx.Check; import net.synedra.validatorfx.Check;
import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@Value @Value
@ -28,19 +29,41 @@ public class WriteModeChoiceComp extends SimpleComp implements Validatable {
public WriteModeChoiceComp(Property<WriteMode> selected, WriteMode[] available) { public WriteModeChoiceComp(Property<WriteMode> selected, WriteMode[] available) {
this.selected = selected; this.selected = selected;
this.available = available; this.available = available;
if (available.length == 1) {
selected.setValue(available[0]);
}
check = Validators.nonNull(validator, I18n.observable("mode"), selected); check = Validators.nonNull(validator, I18n.observable("mode"), selected);
} }
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var a = Arrays.asList(available);
var map = new LinkedHashMap<WriteMode, ObservableValue<String>>(); var map = new LinkedHashMap<WriteMode, ObservableValue<String>>();
map.put(WriteMode.REPLACE, I18n.observable("replace")); var replaceIndex = -1;
map.put(WriteMode.APPEND, I18n.observable("append")); if (a.contains(WriteMode.REPLACE)) {
map.put(WriteMode.PREPEND, I18n.observable("prepend")); map.put(WriteMode.REPLACE, I18n.observable("replace"));
replaceIndex = 0;
}
var appendIndex = -1;
if (a.contains(WriteMode.APPEND)) {
map.put(WriteMode.APPEND, I18n.observable("append"));
appendIndex = replaceIndex + 1;
}
var prependIndex = -1;
if (a.contains(WriteMode.PREPEND)) {
map.put(WriteMode.PREPEND, I18n.observable("prepend"));
prependIndex = Math.max(replaceIndex, appendIndex) + 1;
}
int finalReplaceIndex = replaceIndex;
int finalAppendIndex = appendIndex;
int finalPrependIndex = prependIndex;
return new ToggleGroupComp<>(selected, map).apply(struc -> { return new ToggleGroupComp<>(selected, map).apply(struc -> {
new FancyTooltipAugment<>("extension.replaceDescription").augment(struc.get().getChildren().get(0)); if (finalReplaceIndex != -1) new FancyTooltipAugment<>("extension.replaceDescription").augment(struc.get().getChildren().get(0));
new FancyTooltipAugment<>("extension.appendDescription").augment(struc.get().getChildren().get(1)); if (finalAppendIndex != -1) new FancyTooltipAugment<>("extension.appendDescription").augment(struc.get().getChildren().get(finalAppendIndex));
new FancyTooltipAugment<>("extension.prependDescription").augment(struc.get().getChildren().get(2)); if (finalPrependIndex != -1) new FancyTooltipAugment<>("extension.prependDescription").augment(struc.get().getChildren().get(finalPrependIndex));
}).apply(struc -> check.decorates(struc.get())).createRegion(); }).apply(struc -> check.decorates(struc.get())).createRegion();
} }
} }

View file

@ -47,9 +47,5 @@ public interface PrefsChoiceValue extends Translatable {
return I18n.get(getId()); return I18n.get(getId());
} }
default String toDefaultString() {
return I18n.get(getId());
}
String getId(); String getId();
} }

View file

@ -101,8 +101,6 @@ public class CustomComboBoxBuilder<T> {
cb.valueProperty().addListener((c, o, n) -> { cb.valueProperty().addListener((c, o, n) -> {
if (nodeMap.containsKey(n)) { if (nodeMap.containsKey(n)) {
if (veto != null && !veto.test(nodeMap.get(n))) { if (veto != null && !veto.test(nodeMap.get(n))) {
cb.setValue(o);
;
return; return;
} }
selected.setValue(nodeMap.get(n)); selected.setValue(nodeMap.get(n));

View file

@ -1,15 +1,85 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.ScrollBar; import javafx.scene.control.ScrollBar;
import java.util.Set; import java.util.Set;
public class PrettyListView<T> extends ListView<T> { public class PrettyListView<T> extends ListView<T> {
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}
@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}
@Override
public void selectIndices(int index, int... indices) {
}
@Override
public void selectAll() {
}
@Override
public void selectFirst() {
}
@Override
public void selectLast() {
}
@Override
public void clearAndSelect(int index) {
}
@Override
public void select(int index) {
}
@Override
public void select(T obj) {
}
@Override
public void clearSelection(int index) {
}
@Override
public void clearSelection() {
}
@Override
public boolean isSelected(int index) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void selectPrevious() {
}
@Override
public void selectNext() {
}
}
private final ScrollBar vBar = new ScrollBar(); private final ScrollBar vBar = new ScrollBar();
private final ScrollBar hBar = new ScrollBar(); private final ScrollBar hBar = new ScrollBar();
@ -34,6 +104,10 @@ public class PrettyListView<T> extends ListView<T> {
hBar.visibleProperty().setValue(false); hBar.visibleProperty().setValue(false);
} }
public void disableSelection() {
setSelectionModel(new NoSelectionModel<>());
}
private void bindScrollBars() { private void bindScrollBars() {
final Set<Node> nodes = lookupAll("VirtualScrollBar"); final Set<Node> nodes = lookupAll("VirtualScrollBar");
for (Node node : nodes) { for (Node node : nodes) {

View file

@ -1,5 +1,7 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.I18n; import io.xpipe.extension.I18n;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
@ -11,7 +13,7 @@ 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) {
return v.createCheck().dependsOn("val", s).withMethod(c -> { return v.createCheck().dependsOn("val", s).withMethod(c -> {
if (c.get("val") == null ) { if (c.get("val") == null) {
c.error(I18n.get("extension.mustNotBeEmpty", name.getValue())); c.error(I18n.get("extension.mustNotBeEmpty", name.getValue()));
} }
}); });
@ -23,6 +25,12 @@ public class Validators {
} }
} }
public static void namedStoreExists(DataStore store, String name) {
if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) {
throw new IllegalArgumentException(I18n.get("extension.missingStore", name));
}
}
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) { public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) {
if (!predicate.test(host)) { if (!predicate.test(host)) {
throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name)); throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name));

View file

@ -25,7 +25,9 @@ public interface XPipeDaemon {
public Image image(String file); public Image image(String file);
<T extends Comp<?> & Validatable> T streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider); <T extends Comp<?> & Validatable> T streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider,
boolean showAnonymous,
boolean showSaved);
<T extends Comp<?> & Validatable> T namedStoreChooser( <T extends Comp<?> & Validatable> T namedStoreChooser(
ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category

View file

@ -18,6 +18,7 @@ open module io.xpipe.extension {
requires static javafx.base; requires static javafx.base;
requires static javafx.graphics; requires static javafx.graphics;
requires static javafx.controls; requires static javafx.controls;
requires static javafx.web;
requires static io.xpipe.fxcomps; requires static io.xpipe.fxcomps;
requires static lombok; requires static lombok;
requires static org.controlsfx.controls; requires static org.controlsfx.controls;

View file

@ -7,6 +7,7 @@ nullPointer=Null Pointer: $MSG$
mustNotBeEmpty=$NAME$ must not be empty mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$ hostFeatureUnsupported=Host does not support the feature $FEATURE$
missingStore=$NAME$ does not exist
namedHostFeatureUnsupported=$HOST$ does not support this feature namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available noInformationAvailable=No information available