Various fixes

This commit is contained in:
Christopher Schnick 2022-10-18 01:37:21 +02:00
parent 9e1363c5df
commit a2238be4cd
22 changed files with 323 additions and 190 deletions

View file

@ -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) [![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 ## X-Pipe Java
The fundamental components of the [X-Pipe project](https://docs.xpipe.io). The fundamental components of the [X-Pipe project](https://docs.xpipe.io).
This repository contains the following four modules: 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 - 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 - 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 and the client applications, for example the various programming language APIs and the CLI

View file

@ -68,7 +68,7 @@ public abstract class Charsetter {
if (charset.hasByteOrderMark()) { if (charset.hasByteOrderMark()) {
var bom = stream.readNBytes(charset.getByteOrderMark().length); var bom = stream.readNBytes(charset.getByteOrderMark().length);
if (bom.length != 0 && !Arrays.equals(bom, charset.getByteOrderMark())) { if (bom.length != 0 && !Arrays.equals(bom, charset.getByteOrderMark())) {
throw new IllegalStateException("Invalid charset: " + toString()); throw new IllegalStateException("Charset does not match: " + charset.toString());
} }
} }

View file

@ -16,31 +16,45 @@ public class StreamCharset {
public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null); public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null);
public static final StreamCharset UTF8_BOM = public static final StreamCharset UTF8_BOM =
new StreamCharset(StandardCharsets.UTF_8, new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}); 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_BE = new StreamCharset(StandardCharsets.UTF_16BE, null);
public static final StreamCharset UTF16_BOM = public static final StreamCharset UTF16_BE_BOM =
new StreamCharset(StandardCharsets.UTF_16, new byte[] {(byte) 0xFE, (byte) 0xFF}); 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 = new StreamCharset(StandardCharsets.UTF_16LE, null);
public static final StreamCharset UTF16_LE_BOM = public static final StreamCharset UTF16_LE_BOM =
new StreamCharset(StandardCharsets.UTF_16LE, new byte[] {(byte) 0xFF, (byte) 0xFE}); 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 = public static final StreamCharset UTF32_LE = new StreamCharset(Charset.forName("utf-32le"), null);
new StreamCharset(Charset.forName("utf-32"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF}); 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( public static final List<StreamCharset> COMMON = List.of(
UTF8, UTF8,
UTF8_BOM, UTF8_BOM,
UTF16, UTF16_BE,
UTF16_BOM, UTF16_BE_BOM,
UTF16_LE, UTF16_LE,
UTF16_LE_BOM, UTF16_LE_BOM,
UTF32,
UTF32_BOM,
new StreamCharset(StandardCharsets.US_ASCII, null), new StreamCharset(StandardCharsets.US_ASCII, null),
new StreamCharset(StandardCharsets.ISO_8859_1, null), new StreamCharset(StandardCharsets.ISO_8859_1, null),
new StreamCharset(Charset.forName("Windows-1251"), null), new StreamCharset(Charset.forName("Windows-1251"), null),
new StreamCharset(Charset.forName("Windows-1252"), null)); new StreamCharset(Charset.forName("Windows-1252"), null));
public static final List<StreamCharset> RARE = Charset.availableCharsets().values().stream() private static final List<StreamCharset> RARE_KNOWN = List.of(UTF32_LE, UTF32_LE_BOM, UTF32_BE, UTF32_BE_BOM);
.filter(charset -> COMMON.stream().noneMatch(c -> c.getCharset().equals(charset))) public static final List<StreamCharset> RARE = Stream.concat(
.map(charset -> new StreamCharset(charset, null)) 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(); .toList();
Charset charset; Charset charset;
byte[] byteOrderMark; byte[] byteOrderMark;

View file

@ -45,7 +45,6 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
throw new AssertionError(ex); throw new AssertionError(ex);
} }
} }
public void test() throws Exception { public void test() throws Exception {
store.validate(); store.validate();
} }

View file

@ -51,14 +51,14 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
} }
@Override @Override
public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout) { public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout)); return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout), charset);
} }
@Override @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 { throws Exception {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut)); return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut), charset);
} }
@Override @Override
@ -71,14 +71,15 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
private final List<SecretValue> input; private final List<SecretValue> input;
private final Integer timeout; private final Integer timeout;
private final List<String> command; private final List<String> command;
private Charset charset; private final Charset charset;
private Process process; 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.input = input;
this.timeout = timeout; this.timeout = timeout;
this.command = cmd; this.command = cmd;
this.charset = charset;
} }
private InputStream createInputStream() { private InputStream createInputStream() {
@ -93,7 +94,6 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
var l = type.switchTo(command); var l = type.switchTo(command);
var builder = new ProcessBuilder(l); var builder = new ProcessBuilder(l);
process = builder.start(); process = builder.start();
charset = type.determineCharset(LocalStore.this);
var t = new Thread(() -> { var t = new Thread(() -> {
try (var inputStream = createInputStream()) { try (var inputStream = createInputStream()) {

View file

@ -10,22 +10,54 @@ import java.util.concurrent.atomic.AtomicReference;
public abstract class ProcessControl { public abstract class ProcessControl {
public String readOutOnly() throws Exception { public String executeAndReadStdout() throws Exception {
start(); var pc = this;
var errT = discardErr(); pc.start();
var string = new String(getStdout().readAllBytes(), getCharset()); pc.discardErr();
waitFor(); var bytes = pc.getStdout().readAllBytes();
return string.trim(); var string = new String(bytes, pc.getCharset());
return string;
} }
public Optional<String> readErrOnly() throws Exception { public void executeOrThrow() throws Exception {
start(); var pc = this;
var outT = discardOut(); 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<>(); AtomicReference<String> read = new AtomicReference<>();
var t = new Thread(() -> { var t = new Thread(() -> {
try { try {
read.set(new String(getStderr().readAllBytes(), getCharset())); read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
@ -33,8 +65,17 @@ public abstract class ProcessControl {
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
var ec = waitFor(); var ec = pc.waitFor();
return ec != 0 ? Optional.of(read.get().trim()) : Optional.empty(); 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() { public Thread discardOut() {

View file

@ -2,11 +2,8 @@ package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import java.io.IOException; import java.nio.charset.Charset;
import java.io.UncheckedIOException;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public interface ShellStore extends DataStore { public interface ShellStore extends DataStore {
@ -18,78 +15,6 @@ public interface ShellStore extends DataStore {
return List.of(); 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) { public default Integer getEffectiveTimeOut(Integer timeout) {
if (this.getTimeout() == null) { if (this.getTimeout() == null) {
return timeout; return timeout;
@ -100,18 +25,18 @@ public interface ShellStore extends DataStore {
return Math.min(getTimeout(), timeout); return Math.min(getTimeout(), timeout);
} }
public default ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception { public default ProcessControl prepareCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return prepareCommand(List.of(), cmd, timeout); 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; throws Exception;
public default ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout) throws Exception { public default ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return preparePrivilegedCommand(List.of(), cmd, timeout); 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 { throws Exception {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -18,11 +18,18 @@ public class ShellTypes {
public static final StandardShellStore.ShellType SH = new Sh(); public static final StandardShellStore.ShellType SH = new Sh();
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception { 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")) { if (!o.equals("$0")) {
return SH; return SH;
} else { } 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(); .trim();
if (o.equals("PowerShell")) { if (o.equals("PowerShell")) {
return POWERSHELL; return POWERSHELL;
@ -33,7 +40,8 @@ public class ShellTypes {
} }
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception { 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")) { if (!o.trim().equals("$0")) {
return getLinuxShells(); return getLinuxShells();
} else { } else {
@ -43,7 +51,7 @@ public class ShellTypes {
public static StandardShellStore.ShellType getDefault() { public static StandardShellStore.ShellType getDefault() {
if (System.getProperty("os.name").startsWith("Windows")) { if (System.getProperty("os.name").startsWith("Windows")) {
return CMD; return POWERSHELL;
} else { } else {
return SH; return SH;
} }
@ -71,18 +79,20 @@ public class ShellTypes {
var l = new ArrayList<>(cmd); var l = new ArrayList<>(cmd);
l.add(0, "cmd.exe"); l.add(0, "cmd.exe");
l.add(1, "/c"); l.add(1, "/c");
l.add(2, "@chcp 65001"); l.add(2, "@chcp");
l.add(3, ">"); l.add(3, "65001");
l.add(4, "nul"); l.add(4, ">");
l.add(5, "&&"); l.add(5, "nul");
l.add(6, "&&");
return l; return l;
} }
@Override @Override
public ProcessControl prepareElevatedCommand( 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"); 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 @Override
@ -184,12 +194,13 @@ public class ShellTypes {
@Override @Override
public ProcessControl prepareElevatedCommand( 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); var l = new ArrayList<>(cmd);
l.add(0, "sudo"); l.add(0, "sudo");
l.add(1, "-S"); l.add(1, "-S");
var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st))); 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 @Override

View file

@ -22,7 +22,12 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
public abstract ShellType determineType() throws Exception; public abstract ShellType determineType() throws Exception;
public default String querySystemName() 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(); return result.strip();
} }
@ -30,7 +35,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
public default InputStream openInput(String file) throws Exception { public default InputStream openInput(String file) throws Exception {
var type = determineType(); var type = determineType();
var cmd = type.createFileReadCommand(file); var cmd = type.createFileReadCommand(file);
var p = prepareCommand(List.of(), cmd, null); var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start(); p.start();
return p.getStdout(); return p.getStdout();
} }
@ -49,7 +54,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
public default boolean exists(String file) throws Exception { public default boolean exists(String file) throws Exception {
var type = determineType(); var type = determineType();
var cmd = type.createFileExistsCommand(file); var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(List.of(), cmd, null); var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start(); p.start();
return p.waitFor() == 0; return p.waitFor() == 0;
} }
@ -63,8 +68,9 @@ public interface StandardShellStore extends MachineFileStore, ShellStore {
List<String> switchTo(List<String> cmd); List<String> switchTo(List<String> cmd);
default ProcessControl prepareElevatedCommand( default 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)
return st.prepareCommand(in, cmd, timeout); throws Exception {
return st.prepareCommand(in, cmd, timeout, charset);
} }
List<String> createFileReadCommand(String file); List<String> createFileReadCommand(String file);

View file

@ -19,7 +19,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
default Category getCategory() { default Category getCategory() {
if (getFileProvider() != null) { if (getFileProvider() != null) {
return Category.FILE; return Category.STREAM;
} }
throw new ExtensionException("Provider has no set general type"); throw new ExtensionException("Provider has no set general type");
@ -43,7 +43,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return getId() + "." + key; return getId() + "." + key;
} }
default Region configGui(Property<T> source, Property<T> appliedSource, boolean all) { default Region configGui(Property<T> source, boolean all) {
return null; return null;
} }
@ -120,7 +120,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
List<String> getPossibleNames(); List<String> getPossibleNames();
static enum Category { static enum Category {
FILE, STREAM,
DATABASE; DATABASE;
} }

View file

@ -46,6 +46,11 @@ public interface SupportedApplicationProvider {
PASSIVE PASSIVE
} }
enum Direction {
RETRIEVE,
UPDATE
}
@Value @Value
@AllArgsConstructor @AllArgsConstructor
public static class InstructionsDisplay { public static class InstructionsDisplay {

View file

@ -26,18 +26,20 @@ public class CharsetChoiceComp extends SimpleComp {
}, },
new Label(I18n.get("extension.none")), new Label(I18n.get("extension.none")),
null); null);
builder.addFilter((charset, filter) -> {
return charset.getCharset().displayName().contains(filter);
});
builder.addHeader(I18n.get("extension.common")); builder.addHeader(I18n.get("extension.common"));
for (var e : StreamCharset.COMMON) { for (var e : StreamCharset.COMMON) {
builder.add(e); builder.add(e);
} }
builder.addHeader(I18n.get("extension.other")); builder.addHeader(I18n.get("extension.other"));
builder.addFilter((charset, filter) -> {
return charset.getCharset().displayName().contains(filter);
});
for (var e : StreamCharset.RARE) { for (var e : StreamCharset.RARE) {
builder.add(e); builder.add(e);
} }
return builder.build(); var comboBox = builder.build();
comboBox.setVisibleRowCount(16);
return comboBox;
} }
} }

View file

@ -8,7 +8,6 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent; import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@ -19,7 +18,7 @@ import java.awt.*;
import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection; 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<Boolean> showLineNumbers;
private final ObservableValue<CodeSnippet> value; private final ObservableValue<CodeSnippet> value;
@ -79,7 +78,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
} }
@Override @Override
public CompStructure<StackPane> createBase() { public CompStructure<?> createBase() {
var s = new InlineCssTextArea(); var s = new InlineCssTextArea();
s.setEditable(false); s.setEditable(false);
s.setBackground(null); s.setBackground(null);
@ -88,6 +87,7 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
s.getParent().fireEvent(e); s.getParent().fireEvent(e);
e.consume(); e.consume();
}); });
s.prefHeightProperty().setValue(20* this.value.getValue().lines().stream().count());
var lineNumbers = new VBox(); var lineNumbers = new VBox();
lineNumbers.getStyleClass().add("line-numbers"); lineNumbers.getStyleClass().add("line-numbers");
@ -115,13 +115,10 @@ public class CodeSnippetComp extends Comp<CompStructure<StackPane>> {
} }
}); });
HBox.setHgrow(s, Priority.ALWAYS); HBox.setHgrow(s, Priority.ALWAYS);
var container = new ScrollPane(content);
container.setFitToWidth(true);
container.setFitToHeight(true);
var c = new StackPane(container); var c = new StackPane(content);
container.prefHeightProperty().bind(c.heightProperty());
c.getStyleClass().add("code-snippet-container"); c.getStyleClass().add("code-snippet-container");
c.prefHeightProperty().bind(content.prefHeightProperty());
var copyButton = createCopyButton(c); var copyButton = createCopyButton(c);
var pane = new AnchorPane(copyButton); var pane = new AnchorPane(copyButton);

View file

@ -6,7 +6,6 @@ import io.xpipe.fxcomps.SimpleCompStructure;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -34,7 +33,6 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
flow.setAlignment(Pos.CENTER); flow.setAlignment(Pos.CENTER);
flow.setHgap(7); flow.setHgap(7);
flow.setVgap(7); flow.setVgap(7);
flow.setPadding(new Insets(8, 0, 0, 0));
var nameRegions = new ArrayList<Region>(); var nameRegions = new ArrayList<Region>();
var compRegions = new ArrayList<Region>(); var compRegions = new ArrayList<Region>();

View file

@ -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;
}
}

View file

@ -100,6 +100,10 @@ public class SvgComp {
wv.maxWidthProperty().bind(wv.prefWidthProperty()); wv.maxWidthProperty().bind(wv.prefWidthProperty());
wv.maxHeightProperty().bind(wv.prefHeightProperty()); wv.maxHeightProperty().bind(wv.prefHeightProperty());
wv.minWidthProperty().bind(wv.prefWidthProperty());
wv.minHeightProperty().bind(wv.prefHeightProperty());
return wv; return wv;
} }

View file

@ -5,27 +5,47 @@ import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.PlatformThread; import io.xpipe.fxcomps.util.PlatformThread;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;
public class TextAreaComp extends Comp<CompStructure<TextArea>> { public class TextAreaComp extends Comp<CompStructure<TextArea>> {
private final Property<String> value; private final Property<String> value;
private final Property<String> lazyValue = new SimpleStringProperty();
private final boolean lazy;
public TextAreaComp(Property<String> value) { public TextAreaComp(Property<String> value) {
this(value, false);
}
public TextAreaComp(Property<String> value, boolean lazy) {
this.value = value; this.value = value;
this.lazy = lazy;
if (!lazy) {
value.bind(lazyValue);
}
} }
@Override @Override
public CompStructure<TextArea> createBase() { public CompStructure<TextArea> createBase() {
var text = new TextArea(value.getValue() != null ? value.getValue().toString() : null); var text = new TextArea(value.getValue() != null ? value.getValue().toString() : null);
text.textProperty().addListener((c, o, n) -> { 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) -> { value.addListener((c, o, n) -> {
lazyValue.setValue(n);
PlatformThread.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {
text.setText(n); text.setText(n);
}); });
}); });
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
value.setValue(lazyValue.getValue());
}
});
return new SimpleCompStructure<>(text); return new SimpleCompStructure<>(text);
} }
} }

View file

@ -5,6 +5,7 @@ import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.PlatformThread; import io.xpipe.fxcomps.util.PlatformThread;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
@ -12,39 +13,57 @@ import javafx.scene.input.KeyEvent;
public class TextFieldComp extends Comp<CompStructure<TextField>> { public class TextFieldComp extends Comp<CompStructure<TextField>> {
private final Property<String> value; private final Property<String> lastAppliedValue;
private final Property<String> lazyValue; private final Property<String> currentValue;
private final boolean lazy;
public TextFieldComp(Property<String> value) { public TextFieldComp(Property<String> value) {
this.value = value; this(value, false);
this.lazyValue = value;
} }
public TextFieldComp(Property<String> value, Property<String> lazyValue) { public TextFieldComp(Property<String> value, boolean lazy) {
this.value = value; this.lastAppliedValue = value;
this.lazyValue = lazyValue; this.currentValue = new SimpleStringProperty(value.getValue());
this.lazy = lazy;
if (!lazy) {
value.bind(currentValue);
}
} }
@Override @Override
public CompStructure<TextField> createBase() { 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) -> { 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(() -> { PlatformThread.runLaterIfNeeded(() -> {
text.setText(n); text.setText(n);
}); });
}); });
text.setOnKeyPressed(new EventHandler<KeyEvent>() { text.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override @Override
public void handle(KeyEvent ke) { public void handle(KeyEvent ke) {
if (ke.getCode().equals(KeyCode.ENTER)) { 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(); ke.consume();
} }
}); });
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
lastAppliedValue.setValue(currentValue.getValue());
}
});
return new SimpleCompStructure<>(text); return new SimpleCompStructure<>(text);
} }
} }

View file

@ -23,8 +23,6 @@ public class DynamicOptionsBuilder {
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>(); private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
private final List<Property<?>> props = new ArrayList<>(); private final List<Property<?>> props = new ArrayList<>();
private final List<Property<?>> lazyProperties = new ArrayList<>();
private final ObservableValue<String> title; private final ObservableValue<String> title;
private final boolean wrap; private final boolean wrap;
@ -53,13 +51,6 @@ public class DynamicOptionsBuilder {
return this; 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) { 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()));
@ -133,8 +124,8 @@ public class DynamicOptionsBuilder {
return this; return this;
} }
public DynamicOptionsBuilder addStringArea(String nameKey, Property<String> prop) { public DynamicOptionsBuilder addStringArea(String nameKey, Property<String> prop, boolean lazy) {
var comp = new TextAreaComp(prop); var comp = new TextAreaComp(prop, lazy);
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
props.add(prop); props.add(prop);
return this; return this;
@ -147,11 +138,10 @@ public class DynamicOptionsBuilder {
return this; 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); var comp = new TextFieldComp(prop, lazy);
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp)); entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
props.add(prop); props.add(prop);
lazyProperties.add(lazy);
return this; return this;
} }
@ -162,6 +152,13 @@ public class DynamicOptionsBuilder {
return this; 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) { public DynamicOptionsBuilder addComp(Comp<?> comp) {
return addComp((ObservableValue<String>) null, comp, null); return addComp((ObservableValue<String>) null, comp, null);
} }
@ -220,17 +217,6 @@ public class DynamicOptionsBuilder {
return this; 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( public final <T, V extends T> DynamicOptionsBuilder bindChoice(
Supplier<Property<? extends V>> creator, Property<T> toSet) { Supplier<Property<? extends V>> creator, Property<T> toSet) {
props.forEach(prop -> { props.forEach(prop -> {

View file

@ -24,6 +24,11 @@ public class Validators {
throw new IllegalArgumentException(I18n.get("extension.null", name)); 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) { public static void namedStoreExists(DataStore store, String name) {
if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) { if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) {

View file

@ -23,7 +23,9 @@ public interface XPipeDaemon {
List<DataStore> getNamedStores(); List<DataStore> getNamedStores();
public Image image(String file); Image image(String file);
String svgImage(String file);
<T extends Comp<?> & Validatable> T streamStoreChooser( <T extends Comp<?> & Validatable> T streamStoreChooser(
Property<DataStore> storeProperty, Property<DataStore> storeProperty,
@ -36,7 +38,7 @@ public interface XPipeDaemon {
Property<? extends DataStore> selected, Property<? extends DataStore> selected,
DataStoreProvider.Category category); DataStoreProvider.Category category);
Comp<?> namedSourceChooser( <T extends Comp<?> & Validatable> T namedSourceChooser(
ObservableValue<Predicate<DataSource<?>>> filter, ObservableValue<Predicate<DataSource<?>>> filter,
Property<? extends DataSource<?>> selected, Property<? extends DataSource<?>> selected,
DataSourceProvider.Category category); DataSourceProvider.Category category);

View file

@ -3,6 +3,8 @@ newLine=Newline
crlf=CRLF (Windows) crlf=CRLF (Windows)
lf=LF (Linux) lf=LF (Linux)
none=None none=None
common=Common
other=Other
nullPointer=Null Pointer: $MSG$ nullPointer=Null Pointer: $MSG$
mustNotBeEmpty=$NAME$ must not be empty mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null null=$VALUE$ must be not null