diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index ccab7347..e2c19c86 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -125,6 +125,17 @@ public class BeaconClient implements AutoCloseable { var command = control.command("xpipe beacon --raw").start(); command.discardErr(); return new BeaconClient(command, command.getStdout(), command.getStdin()) { + +// { +// new Thread(() -> { +// while (true) { +// if (!control.isRunning()) { +// close(); +// } +// } +// }) +// } + @Override public T receiveResponse() throws ConnectorException, ClientException, ServerException { diff --git a/core/src/main/java/io/xpipe/core/process/ProcessControl.java b/core/src/main/java/io/xpipe/core/process/ProcessControl.java index 72a521c1..570e3240 100644 --- a/core/src/main/java/io/xpipe/core/process/ProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ProcessControl.java @@ -5,15 +5,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import java.util.List; -import java.util.stream.Collectors; public interface ProcessControl extends Closeable, AutoCloseable { - static String join(List command) { - return command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")); - } - void closeStdin() throws IOException; boolean isStdinClosed(); diff --git a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java index fa1dcd68..a474d76a 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java @@ -71,18 +71,17 @@ public interface ShellProcessControl extends ProcessControl { ShellProcessControl start() throws Exception; default CommandProcessControl commandListFunction(Function> command) { - return commandFunction(shellProcessControl -> command.apply(shellProcessControl).stream() - .map(s -> s.contains(" ") ? "\"" + s + "\"" : s) - .collect(Collectors.joining(" "))); + return commandFunction(shellProcessControl -> shellProcessControl.getShellType().flatten(command.apply(shellProcessControl))); } CommandProcessControl commandFunction(Function command); - CommandProcessControl command(String command); + default CommandProcessControl command(String command){ + return commandFunction(shellProcessControl -> command); + } default CommandProcessControl command(List command) { - return command( - command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "))); + return commandFunction(shellProcessControl -> shellProcessControl.getShellType().flatten(command)); } void exitAndWait() throws IOException; diff --git a/core/src/main/java/io/xpipe/core/process/ShellType.java b/core/src/main/java/io/xpipe/core/process/ShellType.java index 293616d1..1e20577e 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellType.java +++ b/core/src/main/java/io/xpipe/core/process/ShellType.java @@ -5,10 +5,16 @@ import io.xpipe.core.charsetter.NewLine; import java.nio.charset.Charset; import java.util.List; +import java.util.stream.Collectors; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface ShellType { + default String flatten(List command) { + return command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")); + } + + default String joinCommands(String... s) { return String.join(getConcatenationOperator(), s); } diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index 721e544a..1c915737 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -4,8 +4,11 @@ import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.LocalStore; import io.xpipe.core.process.CommandProcessControl; import io.xpipe.core.process.OsType; +import io.xpipe.core.process.ProcessOutputException; import io.xpipe.core.process.ShellProcessControl; +import java.util.List; + public class XPipeInstallation { public static String getInstallationBasePathForCLI(ShellProcessControl p, String cliExecutable) throws Exception { @@ -22,8 +25,10 @@ public class XPipeInstallation { } public static String queryInstallationVersion(ShellProcessControl p, String exec) throws Exception { - try (CommandProcessControl c = p.command(exec + " version").start()) { + try (CommandProcessControl c = p.command(List.of(exec, "version")).start()) { return c.readOrThrow(); + } catch (ProcessOutputException ex) { + return "?"; } } @@ -34,7 +39,7 @@ public class XPipeInstallation { return false; } - try (CommandProcessControl c = p.command(executable + " version").start()) { + try (CommandProcessControl c = p.command(List.of(executable, "version")).start()) { return c.readOrThrow().equals(version); } } diff --git a/extension/src/main/java/io/xpipe/extension/event/EventHandler.java b/extension/src/main/java/io/xpipe/extension/event/EventHandler.java index 0e6097d5..fe8e9144 100644 --- a/extension/src/main/java/io/xpipe/extension/event/EventHandler.java +++ b/extension/src/main/java/io/xpipe/extension/event/EventHandler.java @@ -28,6 +28,25 @@ public abstract class EventHandler { } }; + public static final EventHandler OMIT = new EventHandler() { + @Override + public List snapshotEvents() { + return List.of(); + } + + @Override + public void handle(TrackEvent te) { + } + + @Override + public void handle(ErrorEvent ee) { + } + }; + + public static void set(EventHandler handler) { + INSTANCE = handler; + } + private static EventHandler INSTANCE; private static void init() { diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileStoreChoiceComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileStoreChoiceComp.java new file mode 100644 index 00000000..a6771e1f --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileStoreChoiceComp.java @@ -0,0 +1,77 @@ +package io.xpipe.extension.fxcomps.impl; + +import io.xpipe.core.impl.FileStore; +import io.xpipe.core.impl.LocalStore; +import io.xpipe.core.store.FileSystemStore; +import io.xpipe.core.store.MachineStore; +import io.xpipe.extension.I18n; +import io.xpipe.extension.fxcomps.SimpleComp; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.stage.FileChooser; + +import java.io.File; +import java.util.List; + +public class FileStoreChoiceComp extends SimpleComp { + + private final List availableFileSystems; + private final Property selected; + + public FileStoreChoiceComp(List availableFileSystems, Property selected) { + this.availableFileSystems = availableFileSystems; + this.selected = selected; + } + + private void setSelected(FileSystemStore fileSystemStore, String file) { + selected.setValue(fileSystemStore != null && file != null ? new FileStore(fileSystemStore, file) : null); + } + + @Override + protected Region createSimple() { + var fileProperty = new SimpleStringProperty( + selected.getValue() != null ? selected.getValue().getFile() : null); + var fileSystemProperty = new SimpleObjectProperty<>( + selected.getValue() != null ? selected.getValue().getFileSystem() : availableFileSystems.get(0)); + + fileProperty.addListener((observable, oldValue, newValue) -> { + setSelected(fileSystemProperty.get(), fileProperty.get()); + }); + fileSystemProperty.addListener((observable, oldValue, newValue) -> { + setSelected(fileSystemProperty.get(), fileProperty.get()); + }); + + var fileSystemChoiceComp = new FileSystemStoreChoiceComp(fileSystemProperty); + if (availableFileSystems.size() == 1) { + fileSystemChoiceComp.hide(new SimpleBooleanProperty(true)); + } + + var fileNameComp = new TextFieldComp(fileProperty).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)); + var fileBrowseButton = new IconButtonComp("mdi2f-folder-open-outline", () -> { + if (fileSystemProperty.get() != null && fileSystemProperty.get() instanceof LocalStore) { + var fileChooser = createChooser(); + File file = fileChooser.showOpenDialog(null); + if (file != null && file.exists()) { + fileProperty.setValue(file.toString()); + } + } + }) + .hide(fileSystemProperty.isNotEqualTo(new LocalStore())); + + var layout = new HorizontalComp(List.of(fileSystemChoiceComp, fileNameComp, fileBrowseButton)); + + return layout.createRegion(); + } + + private FileChooser createChooser() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(I18n.get("browseFileTitle")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(I18n.get("anyFile"), "*")); + return fileChooser; + } +} diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileSystemStoreChoiceComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileSystemStoreChoiceComp.java new file mode 100644 index 00000000..180b17ba --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/FileSystemStoreChoiceComp.java @@ -0,0 +1,53 @@ +package io.xpipe.extension.fxcomps.impl; + +import io.xpipe.core.impl.LocalStore; +import io.xpipe.core.store.FileSystemStore; +import io.xpipe.extension.DataStoreProviders; +import io.xpipe.extension.I18n; +import io.xpipe.extension.fxcomps.SimpleComp; +import io.xpipe.extension.util.CustomComboBoxBuilder; +import io.xpipe.extension.util.XPipeDaemon; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +public class FileSystemStoreChoiceComp extends SimpleComp { + + private final Property selected; + + public FileSystemStoreChoiceComp(Property selected) { + this.selected = selected; + } + + private static String getName(FileSystemStore store) { + var name = XPipeDaemon.getInstance().getNamedStores().stream() + .filter(e -> e.equals(store)) + .findAny() + .map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?")) + .orElse(I18n.get("localMachine")); + return name; + } + + private Region createGraphic(FileSystemStore s) { + var provider = DataStoreProviders.byStore(s); + var img = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16); + return new Label(getName(s), img.createRegion()); + } + + @Override + protected Region createSimple() { + var comboBox = new CustomComboBoxBuilder<>(selected, this::createGraphic, null, v -> true); + comboBox.addFilter((v, s) -> getName(v).toLowerCase().contains(s)); + comboBox.add(new LocalStore()); + XPipeDaemon.getInstance().getNamedStores().stream() + .filter(e -> e instanceof FileSystemStore) + .map(e -> (FileSystemStore) e) + .forEach(comboBox::add); + ComboBox cb = comboBox.build(); + cb.getStyleClass().add("choice-comp"); + return cb; + } +} diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/IconButtonComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/IconButtonComp.java new file mode 100644 index 00000000..50d98cdd --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/IconButtonComp.java @@ -0,0 +1,54 @@ +package io.xpipe.extension.fxcomps.impl; + +import com.jfoenix.controls.JFXButton; +import io.xpipe.extension.fxcomps.Comp; +import io.xpipe.extension.fxcomps.CompStructure; +import io.xpipe.extension.fxcomps.SimpleCompStructure; +import io.xpipe.extension.fxcomps.util.PlatformThread; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.css.Size; +import javafx.css.SizeUnits; +import org.kordamp.ikonli.javafx.FontIcon; + +public class IconButtonComp extends Comp> { + + private final ObservableValue icon; + private final Runnable listener; + + public IconButtonComp(String defaultVal) { + this(new SimpleObjectProperty<>(defaultVal), null); + } + + public IconButtonComp(String defaultVal, Runnable listener) { + this(new SimpleObjectProperty<>(defaultVal), listener); + } + + public IconButtonComp(ObservableValue icon, Runnable listener) { + this.icon = PlatformThread.sync(icon); + this.listener = listener; + } + + @Override + public CompStructure createBase() { + var button = new JFXButton(); + + var fi = new FontIcon(icon.getValue()); + icon.addListener((c, o, n) -> { + fi.setIconLiteral(n); + }); + fi.setIconSize((int) new Size(fi.getFont().getSize(), SizeUnits.PT).pixels()); + button.fontProperty().addListener((c, o, n) -> { + fi.setIconSize((int) new Size(n.getSize(), SizeUnits.PT).pixels()); + }); + fi.iconColorProperty().bind(button.textFillProperty()); + button.setGraphic(fi); + button.setOnAction(e -> { + if (listener != null) { + listener.run(); + } + }); + button.getStyleClass().add("icon-button-comp"); + return new SimpleCompStructure<>(button); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java index 7ef2727f..58c70149 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java +++ b/extension/src/main/java/io/xpipe/extension/util/DaemonExtensionTest.java @@ -15,7 +15,7 @@ public class DaemonExtensionTest extends ExtensionTest { } public static DataSource getSource(String type, String file) { - return DataSource.create(null, type, getResource(file)); + return DataSource.create(null, type, getResourceStore(file)); } public static DataSource getSource(io.xpipe.core.source.DataSource source) { diff --git a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java index 2470ba20..98733faa 100644 --- a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java +++ b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java @@ -9,8 +9,18 @@ import java.nio.file.Path; public class ExtensionTest { @SneakyThrows - public static DataStore getResource(String name) { - var url = DaemonExtensionTest.class.getClassLoader().getResource(name); + public static Path getResourcePath(Class c, String name) { + var url = c.getResource(name); + if (url == null) { + throw new IllegalArgumentException(String.format("File %s does not exist", name)); + } + var file = Path.of(url.toURI()); + return file; + } + + @SneakyThrows + public static DataStore getResourceStore(String name) { + var url = ExtensionTest.class.getClassLoader().getResource(name); if (url == null) { throw new IllegalArgumentException(String.format("File %s does not exist", name)); }