mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Various fixes
This commit is contained in:
parent
9e1363c5df
commit
a2238be4cd
22 changed files with 323 additions and 190 deletions
|
@ -1,12 +1,12 @@
|
|||
[![Build Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/build.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/build.yml)
|
||||
[![Publish Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/publishb.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml)
|
||||
[![Publish Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml)
|
||||
|
||||
## X-Pipe Java
|
||||
|
||||
The fundamental components of the [X-Pipe project](https://docs.xpipe.io).
|
||||
This repository contains the following four modules:
|
||||
|
||||
- Core - Shared core classes of the Java API and the X-Pipe daemon implementation
|
||||
- Core - Shared core classes of the Java API, extensions, and the X-Pipe daemon implementation
|
||||
- API - The API that can be used to interact with X-Pipe from any JVM-based languages
|
||||
- Beacon - The X-Pipe beacon component is responsible for handling all communications between the X-Pipe daemon
|
||||
and the client applications, for example the various programming language APIs and the CLI
|
||||
|
|
|
@ -68,7 +68,7 @@ public abstract class Charsetter {
|
|||
if (charset.hasByteOrderMark()) {
|
||||
var bom = stream.readNBytes(charset.getByteOrderMark().length);
|
||||
if (bom.length != 0 && !Arrays.equals(bom, charset.getByteOrderMark())) {
|
||||
throw new IllegalStateException("Invalid charset: " + toString());
|
||||
throw new IllegalStateException("Charset does not match: " + charset.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,31 +16,45 @@ public class StreamCharset {
|
|||
public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null);
|
||||
public static final StreamCharset UTF8_BOM =
|
||||
new StreamCharset(StandardCharsets.UTF_8, new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF});
|
||||
public static final StreamCharset UTF16 = new StreamCharset(StandardCharsets.UTF_16, null);
|
||||
public static final StreamCharset UTF16_BOM =
|
||||
new StreamCharset(StandardCharsets.UTF_16, new byte[] {(byte) 0xFE, (byte) 0xFF});
|
||||
public static final StreamCharset UTF16_BE = new StreamCharset(StandardCharsets.UTF_16BE, null);
|
||||
public static final StreamCharset UTF16_BE_BOM =
|
||||
new StreamCharset(StandardCharsets.UTF_16BE, new byte[] {(byte) 0xFE, (byte) 0xFF});
|
||||
public static final StreamCharset UTF16_LE = new StreamCharset(StandardCharsets.UTF_16LE, null);
|
||||
public static final StreamCharset UTF16_LE_BOM =
|
||||
new StreamCharset(StandardCharsets.UTF_16LE, new byte[] {(byte) 0xFF, (byte) 0xFE});
|
||||
public static final StreamCharset UTF32 = new StreamCharset(Charset.forName("utf-32"), null);
|
||||
public static final StreamCharset UTF32_BOM =
|
||||
new StreamCharset(Charset.forName("utf-32"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF});
|
||||
|
||||
public static final StreamCharset UTF32_LE = new StreamCharset(Charset.forName("utf-32le"), null);
|
||||
public static final StreamCharset UTF32_LE_BOM =
|
||||
new StreamCharset(Charset.forName("utf-32le"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF});
|
||||
public static final StreamCharset UTF32_BE = new StreamCharset(Charset.forName("utf-32be"), null);
|
||||
public static final StreamCharset UTF32_BE_BOM =
|
||||
new StreamCharset(Charset.forName("utf-32be"), new byte[] {(byte) 0xFF, (byte) 0xFE, 0x00, 0x00, });
|
||||
|
||||
public static final List<StreamCharset> COMMON = List.of(
|
||||
UTF8,
|
||||
UTF8_BOM,
|
||||
UTF16,
|
||||
UTF16_BOM,
|
||||
UTF16_BE,
|
||||
UTF16_BE_BOM,
|
||||
UTF16_LE,
|
||||
UTF16_LE_BOM,
|
||||
UTF32,
|
||||
UTF32_BOM,
|
||||
new StreamCharset(StandardCharsets.US_ASCII, null),
|
||||
new StreamCharset(StandardCharsets.ISO_8859_1, null),
|
||||
new StreamCharset(Charset.forName("Windows-1251"), null),
|
||||
new StreamCharset(Charset.forName("Windows-1252"), null));
|
||||
public static final List<StreamCharset> RARE = Charset.availableCharsets().values().stream()
|
||||
.filter(charset -> COMMON.stream().noneMatch(c -> c.getCharset().equals(charset)))
|
||||
.map(charset -> new StreamCharset(charset, null))
|
||||
private static final List<StreamCharset> RARE_KNOWN = List.of(UTF32_LE, UTF32_LE_BOM, UTF32_BE, UTF32_BE_BOM);
|
||||
public static final List<StreamCharset> RARE = Stream.concat(
|
||||
RARE_KNOWN.stream(),
|
||||
Charset.availableCharsets().values().stream()
|
||||
.filter(charset -> !charset.equals(StandardCharsets.UTF_16)
|
||||
&& !charset.equals(Charset.forName("utf-32"))
|
||||
&& !charset.displayName().startsWith("x-")
|
||||
&& !charset.displayName().startsWith("X-")
|
||||
&& !charset.displayName().endsWith("-BOM")
|
||||
&& COMMON.stream()
|
||||
.noneMatch(c -> c.getCharset().equals(charset))
|
||||
&& RARE_KNOWN.stream()
|
||||
.noneMatch(c -> c.getCharset().equals(charset)))
|
||||
.map(charset -> new StreamCharset(charset, null)))
|
||||
.toList();
|
||||
Charset charset;
|
||||
byte[] byteOrderMark;
|
||||
|
|
|
@ -45,7 +45,6 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
|
|||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void test() throws Exception {
|
||||
store.validate();
|
||||
}
|
||||
|
|
|
@ -51,14 +51,14 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
|
|||
}
|
||||
|
||||
@Override
|
||||
public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout) {
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout));
|
||||
public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) {
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout), charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeOut)
|
||||
public ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeOut, Charset charset)
|
||||
throws Exception {
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut));
|
||||
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut), charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,14 +71,15 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
|
|||
private final List<SecretValue> input;
|
||||
private final Integer timeout;
|
||||
private final List<String> command;
|
||||
private Charset charset;
|
||||
private final Charset charset;
|
||||
|
||||
private Process process;
|
||||
|
||||
LocalProcessControl(List<SecretValue> input, List<String> cmd, Integer timeout) {
|
||||
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() {
|
||||
|
@ -93,7 +94,6 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
|
|||
var l = type.switchTo(command);
|
||||
var builder = new ProcessBuilder(l);
|
||||
process = builder.start();
|
||||
charset = type.determineCharset(LocalStore.this);
|
||||
|
||||
var t = new Thread(() -> {
|
||||
try (var inputStream = createInputStream()) {
|
||||
|
|
|
@ -10,22 +10,54 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
public abstract class ProcessControl {
|
||||
|
||||
public String readOutOnly() throws Exception {
|
||||
start();
|
||||
var errT = discardErr();
|
||||
var string = new String(getStdout().readAllBytes(), getCharset());
|
||||
waitFor();
|
||||
return string.trim();
|
||||
public String executeAndReadStdout() throws Exception {
|
||||
var pc = this;
|
||||
pc.start();
|
||||
pc.discardErr();
|
||||
var bytes = pc.getStdout().readAllBytes();
|
||||
var string = new String(bytes, pc.getCharset());
|
||||
return string;
|
||||
}
|
||||
|
||||
public Optional<String> readErrOnly() throws Exception {
|
||||
start();
|
||||
var outT = discardOut();
|
||||
public void executeOrThrow() throws Exception {
|
||||
var pc = this;
|
||||
pc.start();
|
||||
pc.discardOut();
|
||||
pc.discardErr();
|
||||
pc.waitFor();
|
||||
}
|
||||
|
||||
public Optional<String> executeAndReadStderrIfPresent() throws Exception {
|
||||
var pc = this;
|
||||
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()
|
||||
throws Exception {
|
||||
var pc = this;
|
||||
pc.start();
|
||||
|
||||
AtomicReference<String> readError = new AtomicReference<>();
|
||||
var errorThread = new Thread(() -> {
|
||||
try {
|
||||
|
||||
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
errorThread.setDaemon(true);
|
||||
errorThread.start();
|
||||
|
||||
AtomicReference<String> read = new AtomicReference<>();
|
||||
var t = new Thread(() -> {
|
||||
try {
|
||||
read.set(new String(getStderr().readAllBytes(), getCharset()));
|
||||
read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
@ -33,8 +65,17 @@ public abstract class ProcessControl {
|
|||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
var ec = waitFor();
|
||||
return ec != 0 ? Optional.of(read.get().trim()) : Optional.empty();
|
||||
var ec = pc.waitFor();
|
||||
if (ec == -1) {
|
||||
throw new ProcessOutputException("Command timed out");
|
||||
}
|
||||
|
||||
if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
|
||||
return read.get().trim();
|
||||
} else {
|
||||
throw new ProcessOutputException(
|
||||
"Command returned with " + ec + ": " + readError.get().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public Thread discardOut() {
|
||||
|
|
|
@ -2,11 +2,8 @@ package io.xpipe.core.store;
|
|||
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public interface ShellStore extends DataStore {
|
||||
|
||||
|
@ -18,78 +15,6 @@ public interface ShellStore extends DataStore {
|
|||
return List.of();
|
||||
}
|
||||
|
||||
public default String executeAndRead(List<String> cmd, Integer timeout) throws Exception {
|
||||
var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout));
|
||||
pc.start();
|
||||
pc.discardErr();
|
||||
var string = new String(pc.getStdout().readAllBytes(), pc.getCharset());
|
||||
return string;
|
||||
}
|
||||
|
||||
public default String executeAndCheckOut(List<SecretValue> in, List<String> cmd, Integer timeout)
|
||||
throws ProcessOutputException, Exception {
|
||||
var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout));
|
||||
pc.start();
|
||||
|
||||
AtomicReference<String> readError = new AtomicReference<>();
|
||||
var errorThread = new Thread(() -> {
|
||||
try {
|
||||
|
||||
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
errorThread.setDaemon(true);
|
||||
errorThread.start();
|
||||
|
||||
AtomicReference<String> read = new AtomicReference<>();
|
||||
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();
|
||||
if (ec == -1) {
|
||||
throw new ProcessOutputException("Command timed out");
|
||||
}
|
||||
|
||||
if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
|
||||
return read.get().trim();
|
||||
} else {
|
||||
throw new ProcessOutputException(
|
||||
"Command returned with " + ec + ": " + readError.get().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public default Optional<String> executeAndCheckErr(List<SecretValue> in, List<String> cmd) throws Exception {
|
||||
var pc = prepareCommand(in, cmd, getTimeout());
|
||||
pc.start();
|
||||
var outT = pc.discardOut();
|
||||
|
||||
AtomicReference<String> read = new AtomicReference<>();
|
||||
var t = new Thread(() -> {
|
||||
try {
|
||||
read.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
outT.join();
|
||||
t.join();
|
||||
|
||||
var ec = pc.waitFor();
|
||||
return ec != 0 ? Optional.of(read.get()) : Optional.empty();
|
||||
}
|
||||
|
||||
public default Integer getEffectiveTimeOut(Integer timeout) {
|
||||
if (this.getTimeout() == null) {
|
||||
return timeout;
|
||||
|
@ -100,18 +25,18 @@ public interface ShellStore extends DataStore {
|
|||
return Math.min(getTimeout(), timeout);
|
||||
}
|
||||
|
||||
public default ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception {
|
||||
return prepareCommand(List.of(), cmd, 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)
|
||||
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) throws Exception {
|
||||
return preparePrivilegedCommand(List.of(), cmd, timeout);
|
||||
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)
|
||||
public default ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset)
|
||||
throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
@ -18,11 +18,18 @@ public class ShellTypes {
|
|||
public static final StandardShellStore.ShellType SH = new Sh();
|
||||
|
||||
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
|
||||
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null).strip();
|
||||
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII)
|
||||
.executeAndReadStdoutOrThrow()
|
||||
.strip();
|
||||
if (!o.equals("$0")) {
|
||||
return SH;
|
||||
} else {
|
||||
o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null)
|
||||
o = store.prepareCommand(
|
||||
List.of(),
|
||||
List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"),
|
||||
null,
|
||||
StandardCharsets.UTF_16LE)
|
||||
.executeAndReadStdoutOrThrow()
|
||||
.trim();
|
||||
if (o.equals("PowerShell")) {
|
||||
return POWERSHELL;
|
||||
|
@ -33,7 +40,8 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
||||
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
|
||||
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII)
|
||||
.executeAndReadStdoutOrThrow();
|
||||
if (!o.trim().equals("$0")) {
|
||||
return getLinuxShells();
|
||||
} else {
|
||||
|
@ -43,7 +51,7 @@ public class ShellTypes {
|
|||
|
||||
public static StandardShellStore.ShellType getDefault() {
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
return CMD;
|
||||
return POWERSHELL;
|
||||
} else {
|
||||
return SH;
|
||||
}
|
||||
|
@ -71,18 +79,20 @@ public class ShellTypes {
|
|||
var l = new ArrayList<>(cmd);
|
||||
l.add(0, "cmd.exe");
|
||||
l.add(1, "/c");
|
||||
l.add(2, "@chcp 65001");
|
||||
l.add(3, ">");
|
||||
l.add(4, "nul");
|
||||
l.add(5, "&&");
|
||||
l.add(2, "@chcp");
|
||||
l.add(3, "65001");
|
||||
l.add(4, ">");
|
||||
l.add(5, "nul");
|
||||
l.add(6, "&&");
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessControl prepareElevatedCommand(
|
||||
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
|
||||
throws Exception {
|
||||
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
|
||||
return st.prepareCommand(List.of(), l, timeout);
|
||||
return st.prepareCommand(List.of(), l, timeout, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,12 +194,13 @@ public class ShellTypes {
|
|||
|
||||
@Override
|
||||
public ProcessControl prepareElevatedCommand(
|
||||
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
|
||||
throws Exception {
|
||||
var l = new ArrayList<>(cmd);
|
||||
l.add(0, "sudo");
|
||||
l.add(1, "-S");
|
||||
var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st)));
|
||||
return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout);
|
||||
return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,7 +22,12 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
|
|||
public abstract ShellType determineType() throws Exception;
|
||||
|
||||
public default String querySystemName() throws Exception {
|
||||
var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout());
|
||||
var result = prepareCommand(
|
||||
List.of(),
|
||||
determineType().getOperatingSystemNameCommand(),
|
||||
getTimeout(),
|
||||
determineType().determineCharset(this))
|
||||
.executeAndReadStdoutOrThrow();
|
||||
return result.strip();
|
||||
}
|
||||
|
||||
|
@ -30,7 +35,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
|
|||
public default InputStream openInput(String file) throws Exception {
|
||||
var type = determineType();
|
||||
var cmd = type.createFileReadCommand(file);
|
||||
var p = prepareCommand(List.of(), cmd, null);
|
||||
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
|
||||
p.start();
|
||||
return p.getStdout();
|
||||
}
|
||||
|
@ -49,7 +54,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
|
|||
public default boolean exists(String file) throws Exception {
|
||||
var type = determineType();
|
||||
var cmd = type.createFileExistsCommand(file);
|
||||
var p = prepareCommand(List.of(), cmd, null);
|
||||
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
|
||||
p.start();
|
||||
return p.waitFor() == 0;
|
||||
}
|
||||
|
@ -63,8 +68,9 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
|
|||
List<String> switchTo(List<String> cmd);
|
||||
|
||||
default ProcessControl prepareElevatedCommand(
|
||||
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw) throws Exception {
|
||||
return st.prepareCommand(in, cmd, timeout);
|
||||
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);
|
||||
|
|
|
@ -19,7 +19,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
|
||||
default Category getCategory() {
|
||||
if (getFileProvider() != null) {
|
||||
return Category.FILE;
|
||||
return Category.STREAM;
|
||||
}
|
||||
|
||||
throw new ExtensionException("Provider has no set general type");
|
||||
|
@ -43,7 +43,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
return getId() + "." + key;
|
||||
}
|
||||
|
||||
default Region configGui(Property<T> source, Property<T> appliedSource, boolean all) {
|
||||
default Region configGui(Property<T> source, boolean all) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
List<String> getPossibleNames();
|
||||
|
||||
static enum Category {
|
||||
FILE,
|
||||
STREAM,
|
||||
DATABASE;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,11 @@ public interface SupportedApplicationProvider {
|
|||
PASSIVE
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
RETRIEVE,
|
||||
UPDATE
|
||||
}
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
public static class InstructionsDisplay {
|
||||
|
|
|
@ -26,18 +26,20 @@ public class CharsetChoiceComp extends SimpleComp {
|
|||
},
|
||||
new Label(I18n.get("extension.none")),
|
||||
null);
|
||||
builder.addFilter((charset, filter) -> {
|
||||
return charset.getCharset().displayName().contains(filter);
|
||||
});
|
||||
builder.addHeader(I18n.get("extension.common"));
|
||||
for (var e : StreamCharset.COMMON) {
|
||||
builder.add(e);
|
||||
}
|
||||
|
||||
builder.addHeader(I18n.get("extension.other"));
|
||||
builder.addFilter((charset, filter) -> {
|
||||
return charset.getCharset().displayName().contains(filter);
|
||||
});
|
||||
for (var e : StreamCharset.RARE) {
|
||||
builder.add(e);
|
||||
}
|
||||
return builder.build();
|
||||
var comboBox = builder.build();
|
||||
comboBox.setVisibleRowCount(16);
|
||||
return comboBox;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
|
@ -19,7 +18,7 @@ import java.awt.*;
|
|||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
|
||||
public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
||||
public class CodeSnippetComp extends Comp<CompStructure<?>> {
|
||||
|
||||
private final ObservableValue<Boolean> showLineNumbers;
|
||||
private final ObservableValue<CodeSnippet> value;
|
||||
|
@ -79,7 +78,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<StackPane> createBase() {
|
||||
public CompStructure<?> createBase() {
|
||||
var s = new InlineCssTextArea();
|
||||
s.setEditable(false);
|
||||
s.setBackground(null);
|
||||
|
@ -88,6 +87,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
|||
s.getParent().fireEvent(e);
|
||||
e.consume();
|
||||
});
|
||||
s.prefHeightProperty().setValue(20* this.value.getValue().lines().stream().count());
|
||||
|
||||
var lineNumbers = new VBox();
|
||||
lineNumbers.getStyleClass().add("line-numbers");
|
||||
|
@ -115,13 +115,10 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
|
|||
}
|
||||
});
|
||||
HBox.setHgrow(s, Priority.ALWAYS);
|
||||
var container = new ScrollPane(content);
|
||||
container.setFitToWidth(true);
|
||||
container.setFitToHeight(true);
|
||||
|
||||
var c = new StackPane(container);
|
||||
container.prefHeightProperty().bind(c.heightProperty());
|
||||
var c = new StackPane(content);
|
||||
c.getStyleClass().add("code-snippet-container");
|
||||
c.prefHeightProperty().bind(content.prefHeightProperty());
|
||||
|
||||
var copyButton = createCopyButton(c);
|
||||
var pane = new AnchorPane(copyButton);
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.fxcomps.SimpleCompStructure;
|
|||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -34,7 +33,6 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
|||
flow.setAlignment(Pos.CENTER);
|
||||
flow.setHgap(7);
|
||||
flow.setVgap(7);
|
||||
flow.setPadding(new Insets(8, 0, 0, 0));
|
||||
|
||||
var nameRegions = new ArrayList<Region>();
|
||||
var compRegions = new ArrayList<Region>();
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package io.xpipe.extension.comp;
|
||||
|
||||
import io.xpipe.extension.util.XPipeDaemon;
|
||||
import io.xpipe.fxcomps.SimpleComp;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebView;
|
||||
|
||||
public class PrettyImageComp extends SimpleComp {
|
||||
|
||||
private final ObservableValue<String> value;
|
||||
private final double width;
|
||||
private final double height;
|
||||
|
||||
public PrettyImageComp(ObservableValue<String> value, double width, double height) {
|
||||
this.value = value;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var aspectRatioProperty = new SimpleDoubleProperty(1);
|
||||
var widthProperty = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
boolean widthLimited = width / height < aspectRatioProperty.doubleValue();
|
||||
if (widthLimited) {
|
||||
return width;
|
||||
} else {
|
||||
return height * aspectRatioProperty.doubleValue();
|
||||
}
|
||||
},
|
||||
aspectRatioProperty);
|
||||
var heightProperty = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
boolean widthLimited = width / height < aspectRatioProperty.doubleValue();
|
||||
if (widthLimited) {
|
||||
return width / aspectRatioProperty.doubleValue();
|
||||
} else {
|
||||
return height;
|
||||
}
|
||||
},
|
||||
aspectRatioProperty);
|
||||
|
||||
Node node;
|
||||
|
||||
if (value.getValue().endsWith(".svg")) {
|
||||
var storeIcon =
|
||||
SvgComp.create(Bindings.createStringBinding(() -> XPipeDaemon.getInstance().svgImage(value.getValue()), value));
|
||||
aspectRatioProperty.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return storeIcon.getWidth().getValue().doubleValue()
|
||||
/ storeIcon.getHeight().getValue().doubleValue();
|
||||
},
|
||||
storeIcon.getWidth(),
|
||||
storeIcon.getHeight()));
|
||||
node = storeIcon.createWebview();
|
||||
((WebView) node).prefWidthProperty().bind(widthProperty);
|
||||
((WebView) node).maxWidthProperty().bind(widthProperty);
|
||||
((WebView) node).minWidthProperty().bind(widthProperty);
|
||||
((WebView) node).prefHeightProperty().bind(heightProperty);
|
||||
((WebView) node).maxHeightProperty().bind(heightProperty);
|
||||
((WebView) node).minHeightProperty().bind(heightProperty);
|
||||
} else {
|
||||
var storeIcon = new ImageView();
|
||||
storeIcon
|
||||
.imageProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
var image = XPipeDaemon.getInstance().image(value.getValue());
|
||||
aspectRatioProperty.set(image.getWidth() / image.getHeight());
|
||||
return image;
|
||||
},
|
||||
PlatformThread.sync(value)));
|
||||
;
|
||||
storeIcon.fitWidthProperty().bind(widthProperty);
|
||||
storeIcon.fitHeightProperty().bind(heightProperty);
|
||||
storeIcon.setSmooth(true);
|
||||
node = storeIcon;
|
||||
}
|
||||
|
||||
var stack = new StackPane(node);
|
||||
stack.setPrefWidth(width);
|
||||
stack.setMinWidth(width);
|
||||
stack.setPrefHeight(height);
|
||||
stack.setMinHeight(height);
|
||||
stack.setAlignment(Pos.CENTER);
|
||||
return stack;
|
||||
}
|
||||
}
|
|
@ -100,6 +100,10 @@ public class SvgComp {
|
|||
|
||||
wv.maxWidthProperty().bind(wv.prefWidthProperty());
|
||||
wv.maxHeightProperty().bind(wv.prefHeightProperty());
|
||||
|
||||
wv.minWidthProperty().bind(wv.prefWidthProperty());
|
||||
wv.minHeightProperty().bind(wv.prefHeightProperty());
|
||||
|
||||
return wv;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,27 +5,47 @@ import io.xpipe.fxcomps.CompStructure;
|
|||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.scene.control.TextArea;
|
||||
|
||||
public class TextAreaComp extends Comp<CompStructure<TextArea>> {
|
||||
|
||||
private final Property<String> value;
|
||||
private final Property<String> lazyValue = new SimpleStringProperty();
|
||||
private final boolean lazy;
|
||||
|
||||
public TextAreaComp(Property<String> value) {
|
||||
this(value, false);
|
||||
}
|
||||
|
||||
public TextAreaComp(Property<String> value, boolean lazy) {
|
||||
this.value = value;
|
||||
this.lazy = lazy;
|
||||
if (!lazy) {
|
||||
value.bind(lazyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextArea> createBase() {
|
||||
var text = new TextArea(value.getValue() != null ? value.getValue().toString() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? n : null);
|
||||
lazyValue.setValue(n != null && n.length() > 0 ? n : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
lazyValue.setValue(n);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
text.setText(n);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue) {
|
||||
value.setValue(lazyValue.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.fxcomps.CompStructure;
|
|||
import io.xpipe.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
@ -12,39 +13,57 @@ import javafx.scene.input.KeyEvent;
|
|||
|
||||
public class TextFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
private final Property<String> value;
|
||||
private final Property<String> lazyValue;
|
||||
private final Property<String> lastAppliedValue;
|
||||
private final Property<String> currentValue;
|
||||
private final boolean lazy;
|
||||
|
||||
public TextFieldComp(Property<String> value) {
|
||||
this.value = value;
|
||||
this.lazyValue = value;
|
||||
this(value, false);
|
||||
}
|
||||
|
||||
public TextFieldComp(Property<String> value, Property<String> lazyValue) {
|
||||
this.value = value;
|
||||
this.lazyValue = lazyValue;
|
||||
public TextFieldComp(Property<String> value, boolean lazy) {
|
||||
this.lastAppliedValue = value;
|
||||
this.currentValue = new SimpleStringProperty(value.getValue());
|
||||
this.lazy = lazy;
|
||||
if (!lazy) {
|
||||
value.bind(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var text = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
var text = new TextField(
|
||||
currentValue.getValue() != null ? currentValue.getValue().toString() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? n : null);
|
||||
currentValue.setValue(n != null && n.length() > 0 ? n : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
lastAppliedValue.addListener((c, o, n) -> {
|
||||
currentValue.setValue(n);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
text.setText(n);
|
||||
});
|
||||
});
|
||||
|
||||
text.setOnKeyPressed(new EventHandler<KeyEvent>() {
|
||||
@Override
|
||||
public void handle(KeyEvent ke) {
|
||||
if (ke.getCode().equals(KeyCode.ENTER)) {
|
||||
lazyValue.setValue(value.getValue());
|
||||
text.getScene().getRoot().requestFocus();
|
||||
}
|
||||
|
||||
if (lazy && ke.getCode().equals(KeyCode.ENTER)) {
|
||||
lastAppliedValue.setValue(currentValue.getValue());
|
||||
}
|
||||
ke.consume();
|
||||
}
|
||||
});
|
||||
|
||||
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue) {
|
||||
lastAppliedValue.setValue(currentValue.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ public class DynamicOptionsBuilder {
|
|||
|
||||
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
|
||||
private final List<Property<?>> props = new ArrayList<>();
|
||||
private final List<Property<?>> lazyProperties = new ArrayList<>();
|
||||
|
||||
private final ObservableValue<String> title;
|
||||
private final boolean wrap;
|
||||
|
||||
|
@ -53,13 +51,6 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder makeLazy() {
|
||||
var p = props.get(props.size() - 1);
|
||||
props.remove(p);
|
||||
lazyProperties.add(p);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder decorate(Check c) {
|
||||
|
||||
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
|
||||
|
@ -133,8 +124,8 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder addStringArea(String nameKey, Property<String> prop) {
|
||||
var comp = new TextAreaComp(prop);
|
||||
public DynamicOptionsBuilder addStringArea(String nameKey, Property<String> prop, boolean lazy) {
|
||||
var comp = new TextAreaComp(prop, lazy);
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
|
@ -147,11 +138,10 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder addLazyString(String nameKey, Property<String> prop, Property<String> lazy) {
|
||||
public DynamicOptionsBuilder addString(String nameKey, Property<String> prop, boolean lazy) {
|
||||
var comp = new TextFieldComp(prop, lazy);
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
|
||||
props.add(prop);
|
||||
lazyProperties.add(lazy);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -162,6 +152,13 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder addString(ObservableValue<String> name, Property<String> prop, boolean lazy) {
|
||||
var comp = new TextFieldComp(prop, lazy);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder addComp(Comp<?> comp) {
|
||||
return addComp((ObservableValue<String>) null, comp, null);
|
||||
}
|
||||
|
@ -220,17 +217,6 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public <T, V extends T> DynamicOptionsBuilder bindLazy(Supplier<V> creator, Property<T> toLazySet) {
|
||||
lazyProperties.forEach(prop -> {
|
||||
prop.addListener((c, o, n) -> {
|
||||
toLazySet.setValue(creator.get());
|
||||
});
|
||||
});
|
||||
toLazySet.setValue(creator.get());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public final <T, V extends T> DynamicOptionsBuilder bindChoice(
|
||||
Supplier<Property<? extends V>> creator, Property<T> toSet) {
|
||||
props.forEach(prop -> {
|
||||
|
|
|
@ -24,6 +24,11 @@ public class Validators {
|
|||
throw new IllegalArgumentException(I18n.get("extension.null", name));
|
||||
}
|
||||
}
|
||||
public static void notEmpty(String string, String name) {
|
||||
if (string.trim().length() == 0) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.empty", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void namedStoreExists(DataStore store, String name) {
|
||||
if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) {
|
||||
|
|
|
@ -23,7 +23,9 @@ public interface XPipeDaemon {
|
|||
|
||||
List<DataStore> getNamedStores();
|
||||
|
||||
public Image image(String file);
|
||||
Image image(String file);
|
||||
|
||||
String svgImage(String file);
|
||||
|
||||
<T extends Comp<?> & Validatable> T streamStoreChooser(
|
||||
Property<DataStore> storeProperty,
|
||||
|
@ -36,7 +38,7 @@ public interface XPipeDaemon {
|
|||
Property<? extends DataStore> selected,
|
||||
DataStoreProvider.Category category);
|
||||
|
||||
Comp<?> namedSourceChooser(
|
||||
<T extends Comp<?> & Validatable> T namedSourceChooser(
|
||||
ObservableValue<Predicate<DataSource<?>>> filter,
|
||||
Property<? extends DataSource<?>> selected,
|
||||
DataSourceProvider.Category category);
|
||||
|
|
|
@ -3,6 +3,8 @@ newLine=Newline
|
|||
crlf=CRLF (Windows)
|
||||
lf=LF (Linux)
|
||||
none=None
|
||||
common=Common
|
||||
other=Other
|
||||
nullPointer=Null Pointer: $MSG$
|
||||
mustNotBeEmpty=$NAME$ must not be empty
|
||||
null=$VALUE$ must be not null
|
||||
|
|
Loading…
Reference in a new issue