Rework shell stores, other various small changes

This commit is contained in:
Christopher Schnick 2022-11-16 23:40:59 +01:00
parent 02b622fcf9
commit 85389d26f9
40 changed files with 626 additions and 553 deletions

View file

@ -1,13 +1,9 @@
package io.xpipe.api; package io.xpipe.api;
import io.xpipe.core.source.DataSourceInfo;
import java.io.InputStream; import java.io.InputStream;
public interface DataRaw extends DataSource { public interface DataRaw extends DataSource {
DataSourceInfo.Raw getInfo();
InputStream open(); InputStream open();
byte[] readAll(); byte[] readAll();

View file

@ -1,11 +1,7 @@
package io.xpipe.api; package io.xpipe.api;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.source.DataSourceInfo;
public interface DataStructure extends DataSource { public interface DataStructure extends DataSource {
DataSourceInfo.Structure getInfo();
DataStructureNode read(); DataStructureNode read();
} }

View file

@ -2,15 +2,12 @@ package io.xpipe.api;
import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.source.DataSourceInfo;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface DataTable extends Iterable<TupleNode>, DataSource { public interface DataTable extends Iterable<TupleNode>, DataSource {
DataSourceInfo.Table getInfo();
Stream<TupleNode> stream(); Stream<TupleNode> stream();
ArrayNode readAll(); ArrayNode readAll();

View file

@ -1,14 +1,10 @@
package io.xpipe.api; package io.xpipe.api;
import io.xpipe.core.source.DataSourceInfo;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface DataText extends DataSource { public interface DataText extends DataSource {
DataSourceInfo.Text getInfo();
List<String> readAllLines(); List<String> readAllLines();
List<String> readLines(int maxLines); List<String> readLines(int maxLines);

View file

@ -3,27 +3,17 @@ package io.xpipe.api.impl;
import io.xpipe.api.DataRaw; import io.xpipe.api.DataRaw;
import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataSourceConfig;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
import java.io.InputStream; import java.io.InputStream;
public class DataRawImpl extends DataSourceImpl implements DataRaw { public class DataRawImpl extends DataSourceImpl implements DataRaw {
private final DataSourceInfo.Raw info;
public DataRawImpl( public DataRawImpl(
DataSourceId sourceId, DataSourceId sourceId,
DataSourceConfig sourceConfig, DataSourceConfig sourceConfig,
DataSourceInfo.Raw info,
io.xpipe.core.source.DataSource<?> internalSource) { io.xpipe.core.source.DataSource<?> internalSource) {
super(sourceId, sourceConfig, internalSource); super(sourceId, sourceConfig, internalSource);
this.info = info;
}
@Override
public DataSourceInfo.Raw getInfo() {
return info;
} }
@Override @Override

View file

@ -29,27 +29,23 @@ public abstract class DataSourceImpl implements DataSource {
var req = QueryDataSourceExchange.Request.builder().ref(ds).build(); var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
QueryDataSourceExchange.Response res = con.performSimpleExchange(req); QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
var config = new DataSourceConfig(res.getProvider(), res.getConfig()); var config = new DataSourceConfig(res.getProvider(), res.getConfig());
return switch (res.getInfo().getType()) { return switch (res.getType()) {
case TABLE -> { case TABLE -> {
var data = res.getInfo().asTable(); yield new DataTableImpl(res.getId(), config, res.getInternalSource());
yield new DataTableImpl(res.getId(), config, data, res.getInternalSource());
} }
case STRUCTURE -> { case STRUCTURE -> {
var info = res.getInfo().asStructure(); yield new DataStructureImpl(res.getId(), config, res.getInternalSource());
yield new DataStructureImpl(res.getId(), config, info, res.getInternalSource());
} }
case TEXT -> { case TEXT -> {
var info = res.getInfo().asText(); yield new DataTextImpl(res.getId(), config, res.getInternalSource());
yield new DataTextImpl(res.getId(), config, info, res.getInternalSource());
} }
case RAW -> { case RAW -> {
var info = res.getInfo().asRaw(); yield new DataRawImpl(res.getId(), config, res.getInternalSource());
yield new DataRawImpl(res.getId(), config, info, res.getInternalSource());
} }
case COLLECTION -> throw new UnsupportedOperationException( case COLLECTION -> throw new UnsupportedOperationException(
"Unimplemented case: " + res.getInfo().getType()); "Unimplemented case: " + res.getType());
default -> throw new IllegalArgumentException( default -> throw new IllegalArgumentException(
"Unexpected value: " + res.getInfo().getType()); "Unexpected value: " + res.getType());
}; };
}); });
} }

View file

@ -4,20 +4,15 @@ import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.DataStructure; import io.xpipe.api.DataStructure;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
public class DataStructureImpl extends DataSourceImpl implements DataStructure { public class DataStructureImpl extends DataSourceImpl implements DataStructure {
private final DataSourceInfo.Structure info; DataStructureImpl(
public DataStructureImpl(
DataSourceId sourceId, DataSourceId sourceId,
DataSourceConfig sourceConfig, DataSourceConfig sourceConfig,
DataSourceInfo.Structure info,
io.xpipe.core.source.DataSource<?> internalSource) { io.xpipe.core.source.DataSource<?> internalSource) {
super(sourceId, sourceConfig, internalSource); super(sourceId, sourceConfig, internalSource);
this.info = info;
} }
@Override @Override
@ -30,11 +25,6 @@ public class DataStructureImpl extends DataSourceImpl implements DataStructure {
return this; return this;
} }
@Override
public DataSourceInfo.Structure getInfo() {
return info;
}
@Override @Override
public DataStructureNode read() { public DataStructureNode read() {
return null; return null;

View file

@ -13,7 +13,6 @@ import io.xpipe.core.data.typed.TypedAbstractReader;
import io.xpipe.core.data.typed.TypedDataStreamParser; 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.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
@ -25,15 +24,11 @@ import java.util.stream.StreamSupport;
public class DataTableImpl extends DataSourceImpl implements DataTable { public class DataTableImpl extends DataSourceImpl implements DataTable {
private final DataSourceInfo.Table info;
DataTableImpl( DataTableImpl(
DataSourceId id, DataSourceId id,
DataSourceConfig sourceConfig, DataSourceConfig sourceConfig,
DataSourceInfo.Table info,
io.xpipe.core.source.DataSource<?> internalSource) { io.xpipe.core.source.DataSource<?> internalSource) {
super(id, sourceConfig, internalSource); super(id, sourceConfig, internalSource);
this.info = info;
} }
@Override @Override
@ -41,11 +36,6 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
return this; return this;
} }
@Override
public DataSourceInfo.Table getInfo() {
return info;
}
public Stream<TupleNode> stream() { public Stream<TupleNode> stream() {
var iterator = new TableIterator(); var iterator = new TableIterator();
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
@ -93,16 +83,17 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
private TupleNode node; private TupleNode node;
{ {
nodeReader = TypedDataStructureNodeReader.of(info.getDataType());
parser = new TypedDataStreamParser(info.getDataType());
connection = XPipeConnection.open(); connection = XPipeConnection.open();
var req = QueryTableDataExchange.Request.builder() var req = QueryTableDataExchange.Request.builder()
.ref(DataSourceReference.id(getId())) .ref(DataSourceReference.id(getId()))
.maxRows(Integer.MAX_VALUE) .maxRows(Integer.MAX_VALUE)
.build(); .build();
connection.sendRequest(req); connection.sendRequest(req);
connection.receiveResponse(); QueryTableDataExchange.Response response = connection.receiveResponse();
nodeReader = TypedDataStructureNodeReader.of(response.getDataType());
parser = new TypedDataStreamParser(response.getDataType());
connection.receiveBody(); connection.receiveBody();
} }

View file

@ -7,7 +7,6 @@ import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException; import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.api.QueryTextDataExchange; import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
@ -25,15 +24,11 @@ import java.util.stream.StreamSupport;
public class DataTextImpl extends DataSourceImpl implements DataText { public class DataTextImpl extends DataSourceImpl implements DataText {
private final DataSourceInfo.Text info; DataTextImpl(
public DataTextImpl(
DataSourceId sourceId, DataSourceId sourceId,
DataSourceConfig sourceConfig, DataSourceConfig sourceConfig,
DataSourceInfo.Text info,
io.xpipe.core.source.DataSource<?> internalSource) { io.xpipe.core.source.DataSource<?> internalSource) {
super(sourceId, sourceConfig, internalSource); super(sourceId, sourceConfig, internalSource);
this.info = info;
} }
@Override @Override
@ -46,11 +41,6 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
return this; return this;
} }
@Override
public DataSourceInfo.Text getInfo() {
return info;
}
@Override @Override
public List<String> readAllLines() { public List<String> readAllLines() {
return readLines(Integer.MAX_VALUE); return readLines(Integer.MAX_VALUE);

View file

@ -4,8 +4,8 @@ import io.xpipe.beacon.RequestMessage;
import io.xpipe.beacon.ResponseMessage; import io.xpipe.beacon.ResponseMessage;
import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -38,17 +38,14 @@ public class QueryDataSourceExchange implements MessageExchange {
@NonNull @NonNull
DataSourceId id; DataSourceId id;
boolean disabled; @NonNull
boolean hidden; String information;
@NonNull @NonNull
DataSourceInfo info;
@NonNull
String storeDisplay;
String provider; String provider;
@NonNull DataSourceType type;
@NonNull @NonNull
LinkedHashMap<String, String> config; LinkedHashMap<String, String> config;

View file

@ -1,6 +1,7 @@
package io.xpipe.core.charsetter; package io.xpipe.core.charsetter;
import io.xpipe.core.store.FileStore; import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
import lombok.Value; import lombok.Value;
@ -107,10 +108,11 @@ public abstract class Charsetter {
} }
} }
if (store instanceof FileStore fileStore) { if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
var newline = fileStore.getMachine().getNewLine();
if (result.getNewLine() == null) { if (result.getNewLine() == null) {
result = new Result(result.getCharset(), newline); try (var pc = m.create().start()) {
result = new Result(result.getCharset(), pc.getShellType().getNewLine());
}
} }
} }

View file

@ -7,12 +7,14 @@ import io.xpipe.core.source.RawWriteConnection;
import io.xpipe.core.source.WriteMode; import io.xpipe.core.source.WriteMode;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@JsonTypeName("binary") @JsonTypeName("binary")
@SuperBuilder @SuperBuilder
@Jacksonized
public class BinarySource extends RawDataSource<StreamDataStore> { public class BinarySource extends RawDataSource<StreamDataStore> {
@Override @Override

View file

@ -46,8 +46,14 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
throw new AssertionError(ex); throw new AssertionError(ex);
} }
} }
public void test() throws Exception {
store.validate(); public boolean isComplete() {
try {
checkComplete();
return true;
} catch (Exception ignored) {
return false;
}
} }
public void checkComplete() throws Exception { public void checkComplete() throws Exception {

View file

@ -0,0 +1,122 @@
package io.xpipe.core.store;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public interface CommandProcessControl extends ProcessControl {
default InputStream startExternalStdout() throws Exception {
start();
discardErr();
return new FilterInputStream(getStdout()) {
@Override
public void close() throws IOException {
getStdout().close();
CommandProcessControl.this.close();
}
};
}
default OutputStream startExternalStdin() throws Exception {
try (CommandProcessControl pc = start()) {
pc.discardOut();
pc.discardErr();
return new FilterOutputStream(getStdin()) {
@Override
public void close() throws IOException {
pc.getStdin().close();
pc.close();
}
};
} catch (Exception e) {
throw e;
}
}
CommandProcessControl customCharset(Charset charset);
int getExitCode();
CommandProcessControl elevated();
@Override
CommandProcessControl start() throws Exception;
@Override
CommandProcessControl exitTimeout(int timeout);
String readOnlyStdout() throws Exception;
public default void discardOrThrow() throws Exception {
readOrThrow();
}
public default boolean startAndCheckExit() {
try (var pc = start()) {
return pc.discardAndCheckExit();
} catch (Exception e) {
return false;
}
}
public default boolean discardAndCheckExit() {
try {
discardOrThrow();
return true;
} catch (Exception ex) {
return false;
}
}
public default Optional<String> readStderrIfPresent() throws Exception {
discardOut();
var bytes = getStderr().readAllBytes();
var string = new String(bytes, getCharset());
var ec = waitFor();
return ec ? Optional.of(string) : Optional.empty();
}
public default String readOrThrow() throws Exception {
AtomicReference<String> readError = new AtomicReference<>("");
var errorThread = new Thread(() -> {
try {
readError.set(new String(getStderr().readAllBytes(), getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
errorThread.setDaemon(true);
errorThread.start();
AtomicReference<String> read = new AtomicReference<>("");
var t = new Thread(() -> {
try {
read.set(readLine());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
var ec = waitFor();
if (!ec) {
throw new ProcessOutputException("Command timed out");
}
var exitCode = getExitCode();
if (exitCode == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
return read.get().trim();
} else {
throw new ProcessOutputException(
"Command returned with " + ec + ": " + readError.get().trim());
}
}
Thread discardOut();
Thread discardErr();
}

View file

@ -0,0 +1,6 @@
package io.xpipe.core.store;
public interface CommandsStore extends DataStore {
CommandProcessControl create() throws Exception;
}

View file

@ -11,7 +11,7 @@ import java.io.OutputStream;
import java.nio.file.Path; import java.nio.file.Path;
/** /**
* Represents a file located on a certain machine. * Represents a file located on a file system.
*/ */
@JsonTypeName("file") @JsonTypeName("file")
@SuperBuilder @SuperBuilder
@ -19,16 +19,16 @@ import java.nio.file.Path;
@Getter @Getter
public class FileStore extends JacksonizedValue implements FilenameStore, StreamDataStore { public class FileStore extends JacksonizedValue implements FilenameStore, StreamDataStore {
MachineFileStore machine; FileSystemStore fileSystem;
String file; String file;
public FileStore(MachineFileStore machine, String file) { public FileStore(FileSystemStore fileSystem, String file) {
this.machine = machine; this.fileSystem = fileSystem;
this.file = file; this.file = file;
} }
public final boolean isLocal() { public final boolean isLocal() {
return machine instanceof LocalStore; return fileSystem instanceof LocalStore;
} }
public static FileStore local(Path p) { public static FileStore local(Path p) {
@ -44,7 +44,7 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override @Override
public void checkComplete() throws Exception { public void checkComplete() throws Exception {
if (machine == null) { if (fileSystem == null) {
throw new IllegalStateException("Machine is missing"); throw new IllegalStateException("Machine is missing");
} }
if (file == null) { if (file == null) {
@ -54,17 +54,17 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override @Override
public InputStream openInput() throws Exception { public InputStream openInput() throws Exception {
return machine.openInput(file); return fileSystem.openInput(file);
} }
@Override @Override
public OutputStream openOutput() throws Exception { public OutputStream openOutput() throws Exception {
return machine.openOutput(file); return fileSystem.openOutput(file);
} }
@Override @Override
public boolean canOpen() throws Exception { public boolean canOpen() throws Exception {
return machine.exists(file); return fileSystem.exists(file);
} }
@Override @Override

View file

@ -1,11 +1,9 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.charsetter.NewLine;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public interface MachineFileStore extends DataStore { public interface FileSystemStore extends DataStore {
InputStream openInput(String file) throws Exception; InputStream openInput(String file) throws Exception;
@ -13,7 +11,5 @@ public interface MachineFileStore extends DataStore {
public boolean exists(String file) throws Exception; public boolean exists(String file) throws Exception;
void mkdirs(String file) throws Exception; boolean mkdirs(String file) throws Exception;
NewLine getNewLine() throws Exception;
} }

View file

@ -1,21 +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 io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.JacksonizedValue; import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.SecretValue;
import lombok.Getter;
import java.io.*; import java.io.InputStream;
import java.nio.charset.Charset; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@JsonTypeName("local") @JsonTypeName("local")
public class LocalStore extends JacksonizedValue implements MachineFileStore, StandardShellStore { public class LocalStore extends JacksonizedValue implements FileSystemStore, MachineStore {
@Override @Override
public boolean isLocal() { public boolean isLocal() {
@ -28,13 +23,13 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
} }
@Override @Override
public void mkdirs(String file) throws Exception { public boolean mkdirs(String file) throws Exception {
Files.createDirectories(Path.of(file).getParent()); try {
} Files.createDirectories(Path.of(file).getParent());
return true;
@Override } catch (Exception ex) {
public NewLine getNewLine() { return false;
return ShellTypes.getPlatformDefault().getNewLine(); }
} }
@Override @Override
@ -49,98 +44,23 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
var p = Path.of(file); var p = Path.of(file);
return Files.newOutputStream(p); return Files.newOutputStream(p);
} }
@Override @Override
public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) { public ShellProcessControl create() {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout), charset); return LocalProcessControlProvider.create();
} }
@Override public static abstract class LocalProcessControlProvider {
public ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeOut, Charset charset)
throws Exception {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut), charset);
}
@Override private static LocalProcessControlProvider INSTANCE;
public ShellType determineType() throws Exception {
return ShellTypes.getPlatformDefault();
}
class LocalProcessControl extends ProcessControl { public static void init(ModuleLayer layer) {
INSTANCE = ServiceLoader.load(layer, LocalProcessControlProvider.class).findFirst().orElseThrow();
private final List<SecretValue> input;
private final Integer timeout;
@Getter
private final List<String> command;
private final Charset charset;
private Process process;
LocalProcessControl(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) {
this.input = input;
this.timeout = timeout;
this.command = cmd;
this.charset = charset;
} }
private InputStream createInputStream() { public static ShellProcessControl create() {
var string = return INSTANCE.createProcessControl();
input.stream().map(secret -> secret.getSecretValue()).collect(Collectors.joining("\n")) + "\r\n";
return new ByteArrayInputStream(string.getBytes(charset));
} }
@Override public abstract ShellProcessControl createProcessControl();
public void start() throws Exception {
var type = LocalStore.this.determineType();
var l = type.switchTo(command);
var builder = new ProcessBuilder(l);
process = builder.start();
var t = new Thread(() -> {
try (var inputStream = createInputStream()) {
process.getOutputStream().flush();
inputStream.transferTo(process.getOutputStream());
process.getOutputStream().close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
//t.start();
}
@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() {
return process.getInputStream();
}
@Override
public OutputStream getStdin() {
return process.getOutputStream();
}
@Override
public InputStream getStderr() {
return process.getErrorStream();
}
@Override
public Charset getCharset() {
return charset;
}
public Integer getTimeout() {
return timeout;
}
} }
} }

View file

@ -0,0 +1,47 @@
package io.xpipe.core.store;
import java.io.InputStream;
import java.io.OutputStream;
public interface MachineStore extends FileSystemStore, ShellStore {
public default boolean isLocal() {
return false;
}
public default String queryMachineName() throws Exception {
try (CommandProcessControl pc = create().commandListFunction(shellProcessControl ->
shellProcessControl.getShellType().getOperatingSystemNameCommand())
.start()) {
return pc.readOrThrow().trim();
}
}
@Override
public default InputStream openInput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(file))
.startExternalStdout();
}
@Override
public default OutputStream openOutput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileWriteCommand(file))
.startExternalStdin();
}
@Override
public default boolean exists(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createFileExistsCommand(file))
.start()
.discardAndCheckExit();
return r;
}
@Override
public default boolean mkdirs(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createMkdirsCommand(file))
.start()
.discardAndCheckExit();
return r;
}
}

View file

@ -3,129 +3,60 @@ package io.xpipe.core.store;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.UUID;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public abstract class ProcessControl { public interface ProcessControl extends AutoCloseable {
public String executeAndReadStdout() throws Exception { boolean isRunning();
var pc = this;
pc.start();
pc.discardErr();
var bytes = pc.getStdout().readAllBytes();
var string = new String(bytes, pc.getCharset());
pc.waitFor();
return string;
}
public void executeOrThrow() throws Exception { ShellType getShellType();
var pc = this;
pc.start();
pc.discardOut();
pc.discardErr();
pc.waitFor();
}
public boolean executeAndCheckStatus() { String readResultLine(String input, boolean captureOutput) throws IOException;
try {
executeOrThrow(); void writeLine(String line) throws IOException;
return true;
} catch (Exception ex) { void writeLine(String line, boolean captureOutput) throws IOException;
return false;
void typeLine(String line);
public default String readOutput() throws IOException {
var id = UUID.randomUUID();
writeLine("echo " + id, false);
String lines = "";
while (true) {
var newLine = readLine();
if (newLine.contains(id.toString())) {
if (getShellType().echoesInput()) {
readLine();
}
break;
}
lines = lines + newLine + "\n";
} }
return lines;
} }
public Optional<String> executeAndReadStderrIfPresent() throws Exception { @Override
var pc = this; void close() throws IOException;
pc.start();
pc.discardOut();
var bytes = pc.getStderr().readAllBytes();
var string = new String(bytes, pc.getCharset());
var ec = pc.waitFor();
return ec != 0 ? Optional.of(string) : Optional.empty();
}
public String executeAndReadStdoutOrThrow() String readLine() throws IOException;
throws Exception {
var pc = this;
pc.start();
AtomicReference<String> readError = new AtomicReference<>(""); void kill() throws IOException;
var errorThread = new Thread(() -> {
try {
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset())); ProcessControl exitTimeout(int timeout);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
errorThread.setDaemon(true);
errorThread.start();
AtomicReference<String> read = new AtomicReference<>(""); ProcessControl start() throws Exception;
var t = new Thread(() -> {
try {
read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
var ec = pc.waitFor(); boolean waitFor() throws Exception;
if (ec == -1) {
throw new ProcessOutputException("Command timed out");
}
if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) { InputStream getStdout();
return read.get().trim();
} else {
throw new ProcessOutputException(
"Command returned with " + ec + ": " + readError.get().trim());
}
}
public Thread discardOut() { OutputStream getStdin();
var t = new Thread(() -> {
try {
getStdout().transferTo(OutputStream.nullOutputStream());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
return t;
}
public Thread discardErr() { InputStream getStderr();
var t = new Thread(() -> {
try {
getStderr().transferTo(OutputStream.nullOutputStream());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
return t;
}
public abstract void start() throws Exception; Charset getCharset();
public abstract int waitFor() throws Exception;
public abstract InputStream getStdout();
public abstract OutputStream getStdin();
public abstract InputStream getStderr();
public abstract Charset getCharset();
public abstract List<String> getCommand();
} }

View file

@ -0,0 +1,6 @@
package io.xpipe.core.store;
public abstract class ProcessControlProvider {
public abstract ProcessControl local();
}

View file

@ -0,0 +1,58 @@
package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue;
import lombok.NonNull;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public interface ShellProcessControl extends ProcessControl {
ShellProcessControl elevation(SecretValue value);
SecretValue getElevationPassword();
default ShellProcessControl shell(@NonNull ShellType type){
return shell(type.openCommand());
}
default ShellProcessControl shell(@NonNull List<String> command) {
return shell(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
default ShellProcessControl shell(@NonNull String command){
return shell(processControl -> command);
}
ShellProcessControl shell(@NonNull Function<ProcessControl, String> command);
void executeCommand(String command) throws IOException;
default void executeCommand(List<String> command) throws IOException {
executeCommand(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
@Override
ShellProcessControl start() throws Exception;
default CommandProcessControl commandListFunction(Function<ShellProcessControl, List<String>> command) {
return commandFunction(shellProcessControl ->
command.apply(shellProcessControl).stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
CommandProcessControl commandFunction(Function<ShellProcessControl, String> command);
CommandProcessControl command(String command);
default CommandProcessControl command(List<String> command) {
return command(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
void exit() throws IOException;
}

View file

@ -1,43 +1,20 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue; import java.util.concurrent.atomic.AtomicReference;
import java.nio.charset.Charset;
import java.util.List;
public interface ShellStore extends DataStore { public interface ShellStore extends DataStore {
public default Integer getTimeout() { public static MachineStore local() {
return null; return new LocalStore();
} }
public default List<SecretValue> getInput() { ShellProcessControl create();
return List.of();
}
public default Integer getEffectiveTimeOut(Integer timeout) { public default ShellType determineType() throws Exception {
if (this.getTimeout() == null) { AtomicReference<ShellType> type = new AtomicReference<>();
return timeout; try (var pc = create().start()) {
type.set(pc.getShellType());
} }
if (timeout == null) { return type.get();
return getTimeout();
}
return Math.min(getTimeout(), timeout);
}
public default ProcessControl prepareCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return prepareCommand(List.of(), cmd, timeout, charset);
}
public abstract ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset)
throws Exception;
public default ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return preparePrivilegedCommand(List.of(), cmd, timeout, charset);
}
public default ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset)
throws Exception {
throw new UnsupportedOperationException();
} }
} }

View file

@ -0,0 +1,55 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.charsetter.NewLine;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type"
)
public interface ShellType {
void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException;
default void init(ProcessControl proc) throws IOException {
}
default String getExitCommand() {
return "exit";
}
String getExitCodeVariable();
default String getConcatenationOperator() {
return ";";
}
String getEchoCommand(String s, boolean newLine);
List<String> openCommand();
String switchTo(String cmd);
List<String> createMkdirsCommand(String dirs);
List<String> createFileReadCommand(String file);
List<String> createFileWriteCommand(String file);
List<String> createFileExistsCommand(String file);
Charset determineCharset(ProcessControl control) throws Exception;
NewLine getNewLine();
String getName();
String getDisplayName();
List<String> getOperatingSystemNameCommand();
boolean echoesInput();
}

View file

@ -2,54 +2,57 @@ 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.SecretValue;
import lombok.Value; import lombok.Value;
import java.io.ByteArrayInputStream; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
public class ShellTypes { public class ShellTypes {
public static final StandardShellStore.ShellType POWERSHELL = new PowerShell(); public static final ShellType POWERSHELL = new PowerShell();
public static final StandardShellStore.ShellType CMD = new Cmd(); public static final ShellType CMD = new Cmd();
public static final StandardShellStore.ShellType SH = new Sh(); public static final ShellType SH = new Sh();
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception { public static ShellType determine(ProcessControl proc) throws Exception {
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII) proc.writeLine("echo -NoEnumerate \"a\"", false);
.executeAndReadStdoutOrThrow() String line;
.strip(); while (true) {
if (!o.equals("$0")) { line = proc.readLine();
return SH; if (line.equals("-NoEnumerate a")) {
return SH;
}
if (line.contains("echo -NoEnumerate \"a\"")) {
break;
}
}
var o = proc.readLine();
if (o.equals("a")) {
return POWERSHELL;
} else if (o.equals("-NoEnumerate \"a\"")) {
return CMD;
} else { } else {
o = store.prepareCommand( return SH;
List.of(), }
List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), }
null,
StandardCharsets.UTF_16LE) public static ShellType[] getAvailable(ShellStore store) throws Exception {
.executeAndReadStdoutOrThrow() try (ProcessControl proc = store.create().start()) {
.trim(); var type = determine(proc);
if (o.equals("PowerShell")) { if (type == SH) {
return POWERSHELL; return getLinuxShells();
} else { } else {
return CMD; return getWindowsShells();
} }
} }
} }
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception { public static ShellType getRecommendedDefault() {
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII)
.executeAndReadStdoutOrThrow();
if (o.trim().length() > 0 && !o.trim().equals("$0")) {
return getLinuxShells();
} else {
return getWindowsShells();
}
}
public static StandardShellStore.ShellType getRecommendedDefault() {
if (System.getProperty("os.name").startsWith("Windows")) { if (System.getProperty("os.name").startsWith("Windows")) {
return POWERSHELL; return POWERSHELL;
} else { } else {
@ -57,7 +60,7 @@ public class ShellTypes {
} }
} }
public static StandardShellStore.ShellType getPlatformDefault() { public static ShellType getPlatformDefault() {
if (System.getProperty("os.name").startsWith("Windows")) { if (System.getProperty("os.name").startsWith("Windows")) {
return CMD; return CMD;
} else { } else {
@ -65,17 +68,51 @@ public class ShellTypes {
} }
} }
public static StandardShellStore.ShellType[] getWindowsShells() { public static ShellType[] getWindowsShells() {
return new StandardShellStore.ShellType[] {CMD, POWERSHELL}; return new ShellType[] {CMD, POWERSHELL};
} }
public static StandardShellStore.ShellType[] getLinuxShells() { public static ShellType[] getLinuxShells() {
return new StandardShellStore.ShellType[] {SH}; return new ShellType[] {SH};
} }
@JsonTypeName("cmd") @JsonTypeName("cmd")
@Value @Value
public static class Cmd implements StandardShellStore.ShellType { public static class Cmd implements ShellType {
@Override
public String getEchoCommand(String s, boolean newLine) {
return newLine ? "echo " + s : "echo | set /p dummyName=" + s;
}
@Override
public String getConcatenationOperator() {
return "&";
}
@Override
public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
control.executeCommand("net session >NUL 2>NUL");
control.executeCommand("echo %errorLevel%");
var exitCode = Integer.parseInt(control.readLine());
if (exitCode != 0) {
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
}
control.executeCommand(command);
}
@Override
public void init(ProcessControl proc) throws IOException {
proc.readLine();
proc.readLine();
proc.readLine();
}
@Override
public String getExitCodeVariable() {
return "%errorlevel%";
}
@Override @Override
public NewLine getNewLine() { public NewLine getNewLine() {
@ -83,24 +120,18 @@ public class ShellTypes {
} }
@Override @Override
public List<String> switchTo(List<String> cmd) { public List<String> openCommand() {
var l = new ArrayList<>(cmd); return List.of("cmd");
l.add(0, "cmd.exe");
l.add(1, "/c");
l.add(2, "@chcp");
l.add(3, "65001");
l.add(4, ">");
l.add(5, "nul");
l.add(6, "&&");
return l;
} }
@Override @Override
public ProcessControl prepareElevatedCommand( public String switchTo(String cmd) {
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset) return "cmd.exe /c " + cmd;
throws Exception { }
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
return st.prepareCommand(List.of(), l, timeout, charset); @Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("lmkdir", dirs);
} }
@Override @Override
@ -119,8 +150,11 @@ public class ShellTypes {
} }
@Override @Override
public Charset determineCharset(ShellStore store) throws Exception { public Charset determineCharset(ProcessControl control) throws Exception {
return StandardCharsets.UTF_8; var output = control.readResultLine("chcp", true);
var matcher = Pattern.compile("\\d+").matcher(output);
matcher.find();
return Charset.forName("ibm" + matcher.group());
} }
@Override @Override
@ -130,24 +164,63 @@ public class ShellTypes {
@Override @Override
public String getDisplayName() { public String getDisplayName() {
return "cmd.exe"; return "cmd";
} }
@Override @Override
public List<String> getOperatingSystemNameCommand() { public List<String> getOperatingSystemNameCommand() {
return List.of("Get-ComputerInfo"); return List.of("Get-ComputerInfo");
} }
@Override
public boolean echoesInput() {
return true;
}
} }
@JsonTypeName("powershell") @JsonTypeName("powershell")
@Value @Value
public static class PowerShell implements StandardShellStore.ShellType { public static class PowerShell implements ShellType {
@Override @Override
public List<String> switchTo(List<String> cmd) { public boolean echoesInput() {
var l = new ArrayList<>(cmd); return true;
l.add(0, "powershell.exe"); }
return l;
@Override
public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
control.executeCommand("([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)");
var exitCode = Integer.parseInt(control.readLine());
if (exitCode != 0) {
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
}
control.executeCommand(command);
}
@Override
public String getExitCodeVariable() {
return "$LASTEXITCODE";
}
@Override
public String getEchoCommand(String s, boolean newLine) {
return newLine ? "echo " + s : String.format("Write-Host \"%s\" -NoNewLine", s);
}
@Override
public List<String> openCommand() {
return List.of("powershell", "/nologo");
}
@Override
public String switchTo(String cmd) {
return "powershell.exe -Command " + cmd;
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("New-Item", "-Path", "D:\\temp\\Test Folder", "-ItemType", "Directory");
} }
@Override @Override
@ -166,8 +239,11 @@ public class ShellTypes {
} }
@Override @Override
public Charset determineCharset(ShellStore store) throws Exception { public Charset determineCharset(ProcessControl control) throws Exception {
return StandardCharsets.UTF_16LE; var output = control.readResultLine("chcp", true);
var matcher = Pattern.compile("\\d+").matcher(output);
matcher.find();
return Charset.forName("ibm" + matcher.group());
} }
@Override @Override
@ -193,22 +269,41 @@ public class ShellTypes {
@JsonTypeName("sh") @JsonTypeName("sh")
@Value @Value
public static class Sh implements StandardShellStore.ShellType { public static class Sh implements ShellType {
@Override @Override
public List<String> switchTo(List<String> cmd) { public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
return cmd; if (control.getElevationPassword().getSecretValue() == null) {
throw new IllegalStateException("No password for sudo has been set");
}
control.executeCommand("sudo -S " + switchTo(command));
control.writeLine(control.getElevationPassword().getSecretValue());
} }
@Override @Override
public ProcessControl prepareElevatedCommand( public String getExitCodeVariable() {
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset) return "$?";
throws Exception { }
var l = new ArrayList<>(cmd);
l.add(0, "sudo"); @Override
l.add(1, "-S"); public String getEchoCommand(String s, boolean newLine) {
var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st))); return newLine ? "echo " + s : "echo -n " + s;
return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout, charset); }
@Override
public List<String> openCommand() {
return List.of("sh");
}
@Override
public String switchTo(String cmd) {
return "sh -c \"" + cmd + "\"";
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("mkdir", "-p", dirs);
} }
@Override @Override
@ -227,7 +322,7 @@ public class ShellTypes {
} }
@Override @Override
public Charset determineCharset(ShellStore st) throws Exception { public Charset determineCharset(ProcessControl st) throws Exception {
return StandardCharsets.UTF_8; return StandardCharsets.UTF_8;
} }
@ -250,5 +345,10 @@ public class ShellTypes {
public List<String> getOperatingSystemNameCommand() { public List<String> getOperatingSystemNameCommand() {
return List.of("uname", "-o"); return List.of("uname", "-o");
} }
@Override
public boolean echoesInput() {
return false;
}
} }
} }

View file

@ -1,97 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.SecretValue;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
public interface StandardShellStore extends MachineFileStore, ShellStore {
public default ProcessControl prepareLocalCommand(List<SecretValue> input, List<String> cmd, Integer timeout)
throws Exception {
return prepareCommand(input, cmd, timeout, determineType().determineCharset(this));
}
public default boolean isLocal() {
return false;
}
public default NewLine getNewLine() throws Exception {
return determineType().getNewLine();
}
ShellType determineType() throws Exception;
public default String querySystemName() throws Exception {
var result = prepareCommand(
List.of(),
determineType().getOperatingSystemNameCommand(),
getTimeout(),
determineType().determineCharset(this))
.executeAndReadStdoutOrThrow();
return result.strip();
}
@Override
public default InputStream openInput(String file) throws Exception {
var type = determineType();
var cmd = type.createFileReadCommand(file);
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start();
return p.getStdout();
}
@Override
public 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
public default boolean exists(String file) throws Exception {
var type = determineType();
var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start();
return p.waitFor() == 0;
}
@Override
public default void mkdirs(String file) throws Exception {}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public static interface ShellType {
List<String> switchTo(List<String> cmd);
default ProcessControl prepareElevatedCommand(
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
throws Exception {
return st.prepareCommand(in, cmd, timeout, charset);
}
List<String> createFileReadCommand(String file);
List<String> createFileWriteCommand(String file);
List<String> createFileExistsCommand(String file);
Charset determineCharset(ShellStore store) throws Exception;
NewLine getNewLine();
String getName();
String getDisplayName();
List<String> getOperatingSystemNameCommand();
}
}

View file

@ -22,6 +22,7 @@ open module io.xpipe.core {
uses com.fasterxml.jackson.databind.Module; uses com.fasterxml.jackson.databind.Module;
uses io.xpipe.core.source.WriteMode; uses io.xpipe.core.source.WriteMode;
uses io.xpipe.core.store.LocalStore.LocalProcessControlProvider;
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend; provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
provides com.fasterxml.jackson.databind.Module with provides com.fasterxml.jackson.databind.Module with

View file

@ -61,14 +61,13 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return i != -1 ? n.substring(i + 1) : n; return i != -1 ? n.substring(i + 1) : n;
} }
default String queryInformationString(DataStore store, int length) throws Exception {
return getDisplayName();
}
default String getDisplayIconFileName() { default String getDisplayIconFileName() {
return getModuleName() + ":" + getId() + "_icon.png"; return getModuleName() + ":" + getId() + "_icon.png";
} }
default String getSourceDescription(T source) {
return getDisplayName();
}
Dialog configDialog(T source, boolean all); Dialog configDialog(T source, boolean all);
default boolean shouldShow(DataSourceType type) { default boolean shouldShow(DataSourceType type) {

View file

@ -2,7 +2,7 @@ package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.MachineFileStore; import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonizedValue; import io.xpipe.core.util.JacksonizedValue;
@ -28,7 +28,7 @@ public interface DataStoreProvider {
return Category.STREAM; return Category.STREAM;
} }
if (MachineFileStore.class.isAssignableFrom(c) || ShellStore.class.isAssignableFrom(c)) { if (FileSystemStore.class.isAssignableFrom(c) || ShellStore.class.isAssignableFrom(c)) {
return Category.MACHINE; return Category.MACHINE;
} }

View file

@ -1,6 +1,7 @@
package io.xpipe.extension; package io.xpipe.extension;
import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.event.TrackEvent; import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.prefs.PrefsProviders; import io.xpipe.extension.prefs.PrefsProviders;
@ -8,6 +9,8 @@ import io.xpipe.extension.prefs.PrefsProviders;
public class XPipeServiceProviders { public class XPipeServiceProviders {
public static void load(ModuleLayer layer) { public static void load(ModuleLayer layer) {
LocalStore.LocalProcessControlProvider.init(layer);
TrackEvent.info("Loading extension providers ..."); TrackEvent.info("Loading extension providers ...");
DataSourceProviders.init(layer); DataSourceProviders.init(layer);
for (DataSourceProvider<?> p : DataSourceProviders.getAll()) { for (DataSourceProvider<?> p : DataSourceProviders.getAll()) {

View file

@ -4,6 +4,7 @@ import io.xpipe.extension.I18n;
import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.BindingsHelper;
import io.xpipe.fxcomps.util.PlatformThread; import io.xpipe.fxcomps.util.PlatformThread;
import io.xpipe.fxcomps.util.SimpleChangeListener; import io.xpipe.fxcomps.util.SimpleChangeListener;
import javafx.beans.property.Property; import javafx.beans.property.Property;
@ -64,7 +65,8 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
if (!list.contains(null) && includeNone) { if (!list.contains(null) && includeNone) {
list.add(null); list.add(null);
} }
cb.setItems(list);
BindingsHelper.setContent(cb.getItems(), list);
}); });
cb.valueProperty().addListener((observable, oldValue, newValue) -> { cb.valueProperty().addListener((observable, oldValue, newValue) -> {

View file

@ -17,7 +17,7 @@ public abstract class EventHandler {
if (cat == null) { if (cat == null) {
cat = "log"; cat = "log";
} }
System.out.println("[" + cat + "] " + te.getMessage()); System.out.println("[" + cat + "] " + te.toString());
} }
@Override @Override

View file

@ -0,0 +1,18 @@
package io.xpipe.extension.util;
import io.xpipe.api.util.XPipeDaemonController;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
public class DaemonExtensionTest {
@BeforeAll
public static void setup() throws Exception {
XPipeDaemonController.start();
}
@AfterAll
public static void teardown() throws Exception {
XPipeDaemonController.stop();
}
}

View file

@ -33,7 +33,7 @@ public class DialogHelper {
throw new IllegalArgumentException(String.format("Store not found: %s", name)); throw new IllegalArgumentException(String.format("Store not found: %s", name));
} }
if (!(stored.get() instanceof MachineFileStore)) { if (!(stored.get() instanceof FileSystemStore)) {
throw new IllegalArgumentException(String.format("Store not a machine store: %s", name)); throw new IllegalArgumentException(String.format("Store not a machine store: %s", name));
} }

View file

@ -53,7 +53,6 @@ public class DynamicOptionsBuilder {
} }
public DynamicOptionsBuilder decorate(Check c) { public DynamicOptionsBuilder decorate(Check c) {
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get())); entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
return this; return this;
} }

View file

@ -1,22 +1,21 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import io.xpipe.api.DataSource; import io.xpipe.api.DataSource;
import io.xpipe.api.util.XPipeDaemonController;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileStore; import io.xpipe.core.store.FileStore;
import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.XPipeServiceProviders; import io.xpipe.extension.XPipeServiceProviders;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import java.nio.file.Path; import java.nio.file.Path;
public class ExtensionTest { public class ExtensionTest {
@SneakyThrows @SneakyThrows
public static DataStore getResource(String name) { public static DataStore getResource(String name) {
var url = ExtensionTest.class.getClassLoader().getResource(name); var url = DaemonExtensionTest.class.getClassLoader().getResource(name);
if (url == null) { if (url == null) {
throw new IllegalArgumentException(String.format("File %s does not exist", name)); throw new IllegalArgumentException(String.format("File %s does not exist", name));
} }
@ -40,11 +39,5 @@ public class ExtensionTest {
public static void setup() throws Exception { public static void setup() throws Exception {
JacksonMapper.initModularized(ModuleLayer.boot()); JacksonMapper.initModularized(ModuleLayer.boot());
XPipeServiceProviders.load(ModuleLayer.boot()); XPipeServiceProviders.load(ModuleLayer.boot());
XPipeDaemonController.start();
}
@AfterAll
public static void teardown() throws Exception {
XPipeDaemonController.stop();
} }
} }

View file

@ -0,0 +1,4 @@
package io.xpipe.extension.util;
public class LocalExtensionTest extends ExtensionTest {
}

View file

@ -26,7 +26,7 @@ public class OsHelper {
return; return;
} }
ThreadHelper.run(() -> { ThreadHelper.runAsync(() -> {
try { try {
Desktop.getDesktop().open(file.toFile()); Desktop.getDesktop().open(file.toFile());
} catch (Exception e) { } catch (Exception e) {
@ -41,7 +41,7 @@ public class OsHelper {
return; return;
} }
ThreadHelper.run(() -> { ThreadHelper.runAsync(() -> {
try { try {
Desktop.getDesktop().open(file.getParent().toFile()); Desktop.getDesktop().open(file.getParent().toFile());
} catch (Exception e) { } catch (Exception e) {
@ -51,7 +51,7 @@ public class OsHelper {
return; return;
} }
ThreadHelper.run(() -> { ThreadHelper.runAsync(() -> {
try { try {
Desktop.getDesktop().browseFileDirectory(file.toFile()); Desktop.getDesktop().browseFileDirectory(file.toFile());
} catch (Exception e) { } catch (Exception e) {

View file

@ -2,9 +2,6 @@ package io.xpipe.extension.util;
import org.apache.commons.lang3.function.FailableRunnable; import org.apache.commons.lang3.function.FailableRunnable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public class ThreadHelper { public class ThreadHelper {
public static void await(FailableRunnable<InterruptedException> r) { public static void await(FailableRunnable<InterruptedException> r) {
@ -14,26 +11,13 @@ public class ThreadHelper {
} }
} }
public static Thread run(Runnable r) { public static Thread runAsync(Runnable r) {
var t = new Thread(r); var t = new Thread(r);
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
return t; return t;
} }
public static <T> T run(Supplier<T> r) {
AtomicReference<T> ret = new AtomicReference<>();
var t = new Thread(() -> ret.set(r.get()));
t.setDaemon(true);
t.start();
try {
t.join();
} catch (InterruptedException e) {
return null;
}
return ret.get();
}
public static Thread create(String name, boolean daemon, Runnable r) { public static Thread create(String name, boolean daemon, Runnable r) {
var t = new Thread(r); var t = new Thread(r);
t.setDaemon(daemon); t.setDaemon(daemon);

View file

@ -15,7 +15,7 @@ namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available noInformationAvailable=No information available
input=Input input=Input
output=Output output=Output
inout=In/Out inout=Input and Output
inputDescription=This store only produces input for data sources to read inputDescription=This store only produces input for data sources to read
outputDescription=This store only accepts output from data sources to write outputDescription=This store only accepts output from data sources to write
inoutDescription=This store uses both input and output to essentially create a data transformation inoutDescription=This store uses both input and output to essentially create a data transformation