Restructure proc module

This commit is contained in:
crschnick 2023-02-08 21:34:19 +00:00
parent 86c877b219
commit a712c75fc8
84 changed files with 7118 additions and 170 deletions

View file

@ -84,8 +84,22 @@ The other modules make up the X-Pipe implementation and are licensed under GPL:
- [app](app) - Contains the X-Pipe daemon implementation and the X-Pipe desktop application code
- [cli](cli) - The X-Pipe CLI implementation, a GraalVM native image application
- [dist](dist) - Tools to create a distributable package of X-Pipe
- [ext](ext) - Available X-Pipe extensions. Note that essentially every feature is implemented as an extension
- [ext](ext) - Available X-Pipe extensions. Essentially every feature is implemented as an extension
### Open source model
X-Pipe utilizes an open core model, which essentially means that
the main application core is open source while certain other components are not.
In this case these non open source components are planned to be future parts of a potential commercialization.
Furthermore, some tests and especially test environments and that run on private servers
are also not included in this repository (Don't want to leak server information).
Finally, scripts and workflows to create signed executables and installers
are also not included to prevent attackers from easily impersonating the shipping the X-Pipe application malware.
The license model is chosen in such a way that you are
able to use and integrate X-Pipe within your application through the MIT-licensed API.
In any other case where you plan to contribute to the X-Pipe platform itself, which is GPL licensed,
I would still have to figure out how to exactly handle these kinds of contributions.
## Development

View file

@ -89,7 +89,7 @@ List<String> jvmRunArgs = [
"--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.extension",
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
"--add-opens", "java.base/java.lang.reflect=com.jfoenix",
"--add-opens", "java.base/java.lang=io.xpipe.app",
"--add-opens", "java.base/java.lang=io.xpipe.core",
"--add-opens", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.extension",
"-Xmx8g",

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core;
import io.xpipe.app.Main;
import io.xpipe.app.comp.AppLayoutComp;
import io.xpipe.core.process.OsType;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.fxcomps.util.PlatformThread;
@ -10,7 +11,6 @@ import javafx.application.Platform;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import javax.imageio.ImageIO;
import java.awt.*;
@ -38,7 +38,7 @@ public class App extends Application {
// Set dock icon explicitly on mac
// This is necessary in case X-Pipe was started through a script as it will have no icon otherwise
if (SystemUtils.IS_OS_MAC) {
if (OsType.getLocal().equals(OsType.MACOS)) {
try {
var iconUrl = Main.class.getResourceAsStream("resources/img/logo.png");
if (iconUrl != null) {

View file

@ -77,7 +77,7 @@ public class LauncherCommand implements Callable<Integer> {
OpenExchange.Request.builder().arguments(inputs).build());
}
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
con.performSimpleExchange(
OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
@ -120,7 +120,7 @@ public class LauncherCommand implements Callable<Integer> {
LauncherInput.handle(inputs);
// URL open operations have to be handled in a special way on macOS!
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
LauncherInput.handle(List.of(e.getURI().toString()));
});

View file

@ -57,7 +57,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.MAC);
return OsType.getLocal().equals(OsType.MACOS);
}
@Override

View file

@ -5,7 +5,6 @@ import io.xpipe.core.process.ShellTypes;
import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.util.ApplicationHelper;
import io.xpipe.extension.util.WindowsRegistry;
import org.apache.commons.lang3.SystemUtils;
import java.io.IOException;
import java.nio.file.Path;
@ -38,13 +37,9 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@Override
protected Optional<Path> determinePath() {
Optional<String> launcherDir = Optional.empty();
if (SystemUtils.IS_OS_WINDOWS) {
launcherDir = WindowsRegistry.readString(
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null)
.map(p -> p + "\\notepad++.exe");
}
Optional<String> launcherDir;
launcherDir = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null)
.map(p -> p + "\\notepad++.exe");
return launcherDir.map(Path::of);
}
};
@ -71,7 +66,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@Override
public void launch(Path file) throws Exception {
ApplicationHelper.executeLocalApplication(List.of("open", "-a", getApplicationPath().orElseThrow().toString(), file.toString()));
ApplicationHelper.executeLocalApplication(
List.of("open", "-a", getApplicationPath().orElseThrow().toString(), file.toString()));
}
}
@ -92,7 +88,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
var format = customCommand.contains("$file") ? customCommand : customCommand + " $file";
var fileString = file.toString().contains(" ") ? "\"" + file + "\"" : file.toString();
ApplicationHelper.executeLocalApplication(format.replace("$file",fileString));
ApplicationHelper.executeLocalApplication(format.replace("$file", fileString));
}
@Override
@ -108,7 +104,6 @@ public interface ExternalEditorType extends PrefsChoiceValue {
public void launch(Path file) throws Exception;
public static class LinuxPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
public LinuxPathType(String id, String command) {
@ -127,7 +122,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
}
}
public abstract static class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType implements ExternalEditorType {
public abstract static class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType
implements ExternalEditorType {
public WindowsFullPathType(String id) {
super(id);
@ -147,23 +143,23 @@ public interface ExternalEditorType extends PrefsChoiceValue {
public static final List<ExternalEditorType> WINDOWS_EDITORS = List.of(VSCODE, NOTEPADPLUSPLUS_WINDOWS, NOTEPAD);
public static final List<LinuxPathType> LINUX_EDITORS =
List.of(VSCODE_LINUX, NOTEPADPLUSPLUS_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD);
public static final List<ExternalEditorType> MACOS_EDITORS =
List.of(VSCODE_MACOS, SUBLIME_MACOS, TEXT_EDIT);
public static final List<ExternalEditorType> MACOS_EDITORS = List.of(VSCODE_MACOS, SUBLIME_MACOS, TEXT_EDIT);
public static final List<ExternalEditorType> ALL = ((Supplier<List<ExternalEditorType>>) () -> {
var all = new ArrayList<ExternalEditorType>();
if (OsType.getLocal().equals(OsType.WINDOWS)) {
all.addAll(WINDOWS_EDITORS);
}
if (OsType.getLocal().equals(OsType.LINUX)) {
all.addAll(LINUX_EDITORS);
}
if (OsType.getLocal().equals(OsType.MAC)) {
all.addAll(MACOS_EDITORS);
}
all.add(CUSTOM);
return all;
}).get();
var all = new ArrayList<ExternalEditorType>();
if (OsType.getLocal().equals(OsType.WINDOWS)) {
all.addAll(WINDOWS_EDITORS);
}
if (OsType.getLocal().equals(OsType.LINUX)) {
all.addAll(LINUX_EDITORS);
}
if (OsType.getLocal().equals(OsType.MACOS)) {
all.addAll(MACOS_EDITORS);
}
all.add(CUSTOM);
return all;
})
.get();
public static void detectDefault() {
var typeProperty = AppPrefs.get().externalEditor;
@ -190,13 +186,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
}
} else {
typeProperty.set(LINUX_EDITORS.stream()
.filter(externalEditorType -> externalEditorType.isAvailable())
.findFirst()
.orElse(null));
.filter(externalEditorType -> externalEditorType.isAvailable())
.findFirst()
.orElse(null));
}
}
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
typeProperty.set(MACOS_EDITORS.stream()
.filter(externalEditorType -> externalEditorType.isAvailable())
.findFirst()

View file

@ -121,6 +121,7 @@ public class DataStoreEntry extends StorageElement {
var information = Optional.ofNullable(json.get("information"))
.map(JsonNode::textValue)
.orElse(null);
var lastUsed = Instant.parse(json.required("lastUsed").textValue());
var lastModified = Instant.parse(json.required("lastModified").textValue());
var configuration = Optional.ofNullable(json.get("configuration"))

View file

@ -1,16 +1,14 @@
package io.xpipe.app.storage;
import io.xpipe.core.util.XPipeTempDirectory;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;
import lombok.NonNull;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -22,44 +20,7 @@ public class StandardStorage extends DataStorage {
private DataSourceCollection recovery;
private boolean isNewSession() {
try {
if (SystemUtils.IS_OS_WINDOWS) {
var sessionFile = dir.resolve("session");
if (!Files.exists(sessionFile)) {
return true;
}
var lastSessionEndTime = Instant.parse(Files.readString(sessionFile));
var pf = Path.of("C:\\pagefile.sys");
var lastBootTime = Files.getLastModifiedTime(pf).toInstant();
return lastSessionEndTime.isBefore(lastBootTime);
} else {
var sessionFile = XPipeTempDirectory.getLocal().resolve("xpipe_session");
return !Files.exists(sessionFile);
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
return true;
}
}
private void writeSessionInfo() {
try {
if (SystemUtils.IS_OS_WINDOWS) {
var sessionFile = dir.resolve("session");
var now = Instant.now().toString();
Files.writeString(sessionFile, now);
} else {
var sessionFile = XPipeTempDirectory.getLocal().resolve("xpipe_session");
if (!Files.exists(sessionFile)) {
Files.createFile(sessionFile);
}
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
return XPipeSession.get().isNewSystemSession();
}
private void deleteLeftovers() {
@ -322,8 +283,6 @@ public class StandardStorage extends DataStorage {
}
deleteLeftovers();
writeSessionInfo();
}
@Override

View file

@ -63,7 +63,7 @@ public class AppInstaller {
return Files.exists(Path.of("/etc/debian_version")) ? new InstallerAssetType.Debian() : new InstallerAssetType.Rpm();
}
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
return new InstallerAssetType.Pkg();
}
@ -82,7 +82,7 @@ public class AppInstaller {
}
}
if (p.getOsType().equals(OsType.MAC)) {
if (p.getOsType().equals(OsType.MACOS)) {
return new InstallerAssetType.Pkg();
}

View file

@ -6,7 +6,7 @@ import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;

View file

@ -112,9 +112,7 @@ public abstract class Charsetter {
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
if (result.getNewLine() == null) {
try (var pc = m.create().start()) {
result = new Result(result.getCharset(), pc.getShellType().getNewLine());
}
result = new Result(result.getCharset(), m.getShellType() != null ? m.getShellType().getNewLine() : null);
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.core.impl;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.MachineStore;
@ -47,7 +48,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
}
@Override
public ShellProcessControl create() {
public ShellProcessControl createControl() {
return ProcessControlProvider.createLocal();
}

View file

@ -1,50 +0,0 @@
package io.xpipe.core.impl;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellProcessControl;
import lombok.NonNull;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class ProcessControlProvider {
private static List<ProcessControlProvider> INSTANCES;
public static void init(ModuleLayer layer) {
INSTANCES = ServiceLoader.load(layer, ProcessControlProvider.class)
.stream().map(localProcessControlProviderProvider -> localProcessControlProviderProvider.get()).toList();
}
public static ShellProcessControl createLocal() {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl()).findFirst().orElseThrow();
}
public static ShellProcessControl createSub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.sub(parent, commandFunction, terminalCommand)).findFirst().orElseThrow();
}
public static CommandProcessControl createCommand(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.command(parent, command, terminalCommand)).findFirst().orElseThrow();
}
public abstract ShellProcessControl sub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand);
public abstract CommandProcessControl command(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand);
public abstract ShellProcessControl createLocalProcessControl();
}

View file

@ -7,12 +7,12 @@ public interface OsType {
Windows WINDOWS = new Windows();
Linux LINUX = new Linux();
Mac MAC = new Mac();
MacOs MACOS = new MacOs();
public static OsType getLocal() {
String osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
if ((osName.contains("mac")) || (osName.contains("darwin"))) {
return MAC;
return MACOS;
} else if (osName.contains("win")) {
return WINDOWS;
} else if (osName.contains("nux")) {
@ -124,7 +124,7 @@ public interface OsType {
}
}
static class Mac implements OsType {
static class MacOs implements OsType {
@Override
public String getTempDirectory(ShellProcessControl pc) throws Exception {

View file

@ -1,6 +1,58 @@
package io.xpipe.core.process;
import lombok.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class ProcessControlProvider {
public abstract ProcessControl local();
private static List<ProcessControlProvider> INSTANCES;
public static void init(ModuleLayer layer) {
INSTANCES = ServiceLoader.load(layer, ProcessControlProvider.class)
.stream().map(localProcessControlProviderProvider -> localProcessControlProviderProvider.get()).toList();
}
public static ShellProcessControl createLocal() {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl()).findFirst().orElseThrow();
}
public static ShellProcessControl createSub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.sub(parent, commandFunction, terminalCommand)).filter(
Objects::nonNull).findFirst().orElseThrow();
}
public static CommandProcessControl createCommand(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.command(parent, command, terminalCommand)).filter(
Objects::nonNull).findFirst().orElseThrow();
}
public static ShellProcessControl createSsh(Object sshStore) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.createSshControl(sshStore)).filter(
Objects::nonNull).findFirst().orElseThrow();
}
public abstract ShellProcessControl sub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand);
public abstract CommandProcessControl command(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand);
public abstract ShellProcessControl createLocalProcessControl();
public abstract ShellProcessControl createSshControl(Object sshStore);
}

View file

@ -6,11 +6,14 @@ import lombok.NonNull;
import java.io.IOException;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public interface ShellProcessControl extends ProcessControl {
void onInit(Consumer<ShellProcessControl> pc);
String prepareTerminalOpen() throws Exception;
String prepareIntermediateTerminalOpen(String content) throws Exception;

View file

@ -199,18 +199,15 @@ public class ShellTypes {
@Override
public Charset determineCharset(ShellProcessControl control) throws Exception {
control.writeLine("chcp");
var pattern = Pattern.compile("^[\\w ]+: (\\d+)$");
var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII));
// Read echo of command
r.readLine();
// Read actual output
var line = r.readLine();
// Read additional empty line
r.readLine();
var matcher = Pattern.compile("\\d+").matcher(line);
matcher.find();
return Charset.forName("ibm" + matcher.group());
while (true) {
var line = r.readLine();
var matcher = pattern.matcher(line);
if (matcher.matches()) {
return Charset.forName("ibm" + matcher.group(1));
}
}
}
@Override

View file

@ -2,10 +2,14 @@ package io.xpipe.core.store;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
public interface ShellStore extends DataStore {
import java.nio.charset.Charset;
public interface ShellStore extends DataStore, StatefulDataStore
{
public static MachineStore local() {
return new LocalStore();
@ -21,7 +25,28 @@ public interface ShellStore extends DataStore {
return s instanceof LocalStore;
}
ShellProcessControl create();
default ShellProcessControl create() {
var pc = createControl();
pc.onInit(processControl -> {
setState("type", processControl.getShellType());
setState("os", processControl.getOsType());
setState("charset", processControl.getCharset());
});
return pc;
}
default ShellType getShellType() {
return getState("type", ShellType.class, null);
}
default OsType getOsType() {
return getState("os", OsType.class, null);
}
default Charset getCharset() {
return getState("charset", Charset.class, null);
}
ShellProcessControl createControl();
public default ShellType determineType() throws Exception {
try (var pc = create().start()) {

View file

@ -20,7 +20,7 @@ public class XPipeInstallation {
if (OsType.getLocal().equals(OsType.LINUX)) {
return "nohup \"" + installationBase + "/app/bin/xpiped\" --mode " + mode.getDisplayName() + suffix
+ " & disown";
} else if (OsType.getLocal().equals(OsType.MAC)) {
} else if (OsType.getLocal().equals(OsType.MACOS)) {
return "open \"" + installationBase + "\" --args --mode " + mode.getDisplayName() + suffix;
}
@ -55,7 +55,7 @@ public class XPipeInstallation {
public static boolean isInstallationDistribution() {
var base = getLocalInstallationBasePath();
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
if (!base.toString().equals(getLocalDefaultInstallationBasePath(false))) {
return false;
}
@ -93,13 +93,13 @@ public class XPipeInstallation {
public static Path getLocalExtensionsDirectory() {
Path path = getLocalInstallationBasePath();
return OsType.getLocal().equals(OsType.MAC)
return OsType.getLocal().equals(OsType.MACOS)
? path.resolve("Contents").resolve("Resources").resolve("extensions")
: path.resolve("app").resolve("extensions");
}
private static Path getLocalInstallationBasePathForJavaExecutable(Path executable) {
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
return executable
.getParent()
.getParent()
@ -115,7 +115,7 @@ public class XPipeInstallation {
}
private static Path getLocalInstallationBasePathForDaemonExecutable(Path executable) {
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
return executable.getParent().getParent().getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return executable.getParent().getParent().getParent();
@ -138,7 +138,7 @@ public class XPipeInstallation {
return defaultInstallation;
}
if (OsType.getLocal().equals(OsType.MAC)) {
if (OsType.getLocal().equals(OsType.MACOS)) {
return FileNames.getParent(FileNames.getParent(FileNames.getParent(cliExecutable)));
} else {
return FileNames.getParent(FileNames.getParent(cliExecutable));

View file

@ -26,7 +26,7 @@ public class XPipeTempDirectory {
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileExistsCommand(dir))) {
proc.executeSimpleCommand(proc.getShellType().flatten(proc.getShellType().getMkdirsCommand(dir)), "Unable to access or create temporary directory " + dir);
if (proc.getOsType().equals(OsType.LINUX) || proc.getOsType().equals(OsType.MAC)) {
if (proc.getOsType().equals(OsType.LINUX) || proc.getOsType().equals(OsType.MACOS)) {
proc.executeSimpleCommand("chmod -f 777 \"" + dir + "\"");
}
}

View file

@ -1,4 +1,4 @@
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.source.WriteMode;
import io.xpipe.core.util.CoreJacksonModule;

View file

@ -3,7 +3,6 @@ package io.xpipe.ext.pdx.parser;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.node.ValueNode;
import org.apache.commons.lang3.SystemUtils;
import java.io.IOException;
import java.nio.charset.Charset;
@ -40,7 +39,7 @@ public final class TextFormatParser {
public static TextFormatParser eu4() {
return new TextFormatParser(
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
Charset.forName("windows-1252"),
TaggedNodes.NO_TAGS,
s -> s.equals("map_area_data"));
}
@ -59,14 +58,14 @@ public final class TextFormatParser {
public static TextFormatParser ck2() {
return new TextFormatParser(
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
Charset.forName("windows-1252"),
TaggedNodes.NO_TAGS,
s -> false);
}
public static TextFormatParser vic2() {
return new TextFormatParser(
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
Charset.forName("windows-1252"),
TaggedNodes.NO_TAGS,
s -> false);
}

39
ext/proc/build.gradle Normal file
View file

@ -0,0 +1,39 @@
plugins {
id 'java'
id "org.moditect.gradleplugin" version "1.0.0-rc3"
}
apply from: "$rootDir/gradle/gradle_scripts/java.gradle"
apply from: "$rootDir/gradle/gradle_scripts/commons.gradle"
apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle"
apply from: "$rootDir/gradle/gradle_scripts/extension.gradle"
tasks.withType(JavaCompile).configureEach {
doFirst {
options.compilerArgs += [
'--module-path', classpath.asPath
]
classpath = files()
}
}
configurations {
compileOnly.extendsFrom(dep)
}
dependencies {
compileOnly project(':app')
testImplementation project(':app')
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-exec:1.3'
compileOnly group: 'com.dlsc.preferencesfx', name: 'preferencesfx-core', version: '11.15.0'
compileOnly group: 'com.dlsc.formsfx', name: 'formsfx-core', version: '11.6.0'
}
List<String> jvmDevArgs = [
"--add-reads", "io.xpipe.ext.proc=ALL-UNNAMED"
]
tasks.withType(Test) {
jvmArgs += jvmDevArgs
}

View file

@ -0,0 +1,241 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ProcessOutputException;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.extension.event.TrackEvent;
import lombok.NonNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class CommandControlImpl extends ProcessControlImpl implements CommandProcessControl {
private static final ExecutorService stdoutReader = Executors.newFixedThreadPool(1, new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
t.setName("stdout reader");
return t;
}
});
private static final ExecutorService stderrReader = Executors.newFixedThreadPool(1, new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
t.setName("stderr reader");
return t;
}
});
protected final ShellProcessControl parent;
@NonNull
protected final Function<ShellProcessControl, String> command;
protected final Function<ShellProcessControl, String> terminalCommand;
protected boolean elevated;
protected int exitCode = -1;
protected String timedOutError;
protected Integer exitTimeout;
protected boolean complex;
protected boolean obeysReturnValueConvention = true;
public CommandControlImpl(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand) {
this.command = command;
this.parent = parent;
this.terminalCommand = terminalCommand;
}
@Override
public CommandProcessControl doesNotObeyReturnValueConvention() {
this.obeysReturnValueConvention = false;
return this;
}
@Override
public CommandProcessControl sensitive() {
this.sensitive = true;
return this;
}
public int getExitCode() {
if (running) {
waitFor();
}
return exitCode;
}
@Override
public CommandProcessControl exitTimeout(Integer timeout) {
this.exitTimeout = timeout;
return this;
}
@Override
public CommandProcessControl customCharset(Charset charset) {
this.charset = charset;
return this;
}
@Override
public CommandProcessControl elevated() {
this.elevated = true;
return this;
}
@Override
public ShellType getShellType() {
return parent.getShellType();
}
@Override
public CommandProcessControl complex() {
this.complex = true;
return this;
}
public void discardOut() {
stdoutReader.submit(() -> {
try {
var read = new String(getStdout().readAllBytes(), getCharset());
TrackEvent.withTrace("proc", "Discarding stdout")
.tag("output", read)
.handle();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
public void discardErr() {
stderrReader.submit(() -> {
try {
var read = new String(getStderr().readAllBytes(), getCharset());
TrackEvent.withTrace("proc", "Discarding stderr")
.tag("output", read)
.handle();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
public String readOnlyStdout() throws Exception {
discardErr();
var bytes = getStdout().readAllBytes();
var string = new String(bytes, getCharset());
TrackEvent.withTrace("proc", "Read stdout").tag("output", string).handle();
return string.trim();
}
@Override
public void accumulateStdout(Consumer<String> con) {
stderrReader.submit(() -> {
try {
var out = new String(getStdout().readAllBytes(), getCharset()).strip();
TrackEvent.withTrace("proc", "Read stdout").tag("output", out).handle();
con.accept(out);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
@Override
public void accumulateStderr(Consumer<String> con) {
stderrReader.submit(() -> {
try {
var err = new String(getStderr().readAllBytes(), getCharset()).strip();
TrackEvent.withTrace("proc", "Read stderr").tag("output", err).handle();
con.accept(err);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
public String readOrThrow() throws Exception {
AtomicReference<String> read = new AtomicReference<>("");
stdoutReader.submit(() -> {
try {
var bytes = getStdout().readAllBytes();
read.set(new String(bytes, getCharset()));
TrackEvent.withTrace("proc", "Read stdout")
.tag("output", read.get())
.handle();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
AtomicReference<String> readError = new AtomicReference<>("");
stderrReader.submit(() -> {
try {
readError.set(new String(getStderr().readAllBytes(), getCharset()));
TrackEvent.withTrace("proc", "Read stderr")
.tag("output", readError.get())
.handle();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
var ec = waitFor();
if (!ec) {
throw new ProcessOutputException("Command timed out" + (timedOutError != null ? ": " + timedOutError : ""));
}
var exitCode = getExitCode();
var success = (obeysReturnValueConvention && exitCode == 0) || (!obeysReturnValueConvention && !(read.get().isEmpty() && !readError.get().isEmpty()));
if (success) {
return read.get().trim();
} else {
throw new ProcessOutputException(
"Command returned with " + exitCode + ": " + readError.get().trim());
}
}
public void discardOrThrow() throws Exception {
AtomicReference<String> read = new AtomicReference<>("");
stdoutReader.submit(() -> {
try {
getStdout().transferTo(OutputStream.nullOutputStream());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
AtomicReference<String> readError = new AtomicReference<>("");
stderrReader.submit(() -> {
try {
readError.set(new String(getStderr().readAllBytes(), getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
var ec = waitFor();
if (!ec) {
throw new ProcessOutputException("Command timed out" + (timedOutError != null ? ": " + timedOutError : ""));
}
var exitCode = getExitCode();
var success = (obeysReturnValueConvention && exitCode == 0) || (!obeysReturnValueConvention && !(read.get().isEmpty() && !readError.get().isEmpty()));
if (!success) {
throw new ProcessOutputException(
"Command returned with " + exitCode + ": " + readError.get().trim());
}
}
}

View file

@ -0,0 +1,288 @@
package io.xpipe.ext.proc;
import io.xpipe.app.prefs.ExternalApplicationType;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ApplicationHelper;
import java.util.List;
public interface ExternalTerminalType extends PrefsChoiceValue {
public static final ExternalTerminalType CMD = new SimpleType("proc.cmd", "cmd", "cmd.exe") {
@Override
protected String toCommand(String name, String command) {
return "cmd.exe /C " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.WINDOWS);
}
};
public static final ExternalTerminalType POWERSHELL =
new SimpleType("proc.powershell", "powershell", "PowerShell") {
@Override
protected String toCommand(String name, String command) {
return "powershell.exe -Command " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.WINDOWS);
}
};
public static final ExternalTerminalType WINDOWS_TERMINAL =
new SimpleType("proc.windowsTerminal", "wt.exe", "Windows Terminal") {
@Override
protected String toCommand(String name, String command) {
return "-w 1 nt --title \"" + name + "\" " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.WINDOWS);
}
};
public static final ExternalTerminalType GNOME_TERMINAL =
new SimpleType("proc.gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
@Override
protected String toCommand(String name, String command) {
return "--title \"" + name + "\" -- " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.LINUX);
}
};
public static final ExternalTerminalType KONSOLE = new SimpleType("proc.konsole", "konsole", "Konsole") {
@Override
protected String toCommand(String name, String command) {
return "--new-tab -e bash -c " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.LINUX);
}
};
public static final ExternalTerminalType XFCE = new SimpleType("proc.xfce", "xfce4-terminal", "Xfce") {
@Override
protected String toCommand(String name, String command) {
return "--tab --title \"" + name + "\" --command " + command;
}
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.LINUX);
}
};
public static final ExternalTerminalType MACOS_TERMINAL = new MacOsTerminalType();
public static final ExternalTerminalType ITERM2 = new ITerm2Type();
public static final ExternalTerminalType WARP = new WarpType();
public static final ExternalTerminalType CUSTOM = new CustomType();
public static final List<ExternalTerminalType> ALL = List.of(
WINDOWS_TERMINAL,
POWERSHELL,
CMD,
KONSOLE,
XFCE,
GNOME_TERMINAL,
WARP,
ITERM2,
MACOS_TERMINAL,
CUSTOM)
.stream()
.filter(terminalType -> terminalType.isSelectable())
.toList();
public static ExternalTerminalType getDefault() {
return ALL.stream()
.filter(terminalType -> terminalType.isAvailable())
.findFirst()
.orElse(null);
}
public abstract void launch(String name, String command) throws Exception;
static class MacOsTerminalType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
public MacOsTerminalType() {
super("proc.macosTerminal", "Terminal");
}
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
var suffix = command.equals(pc.getShellType().getNormalOpenCommand())
? "\"\""
: "\"" + command.replaceAll("\"", "\\\\\"") + "\"";
var cmd = "osascript -e 'tell app \"" + "Terminal" + "\" to do script " + suffix + "'";
pc.executeSimpleCommand(cmd);
}
}
}
static class CustomType extends ExternalApplicationType implements ExternalTerminalType {
public CustomType() {
super("proc.custom");
}
@Override
public void launch(String name, String command) throws Exception {
var custom =
PrefsProvider.get(ProcPrefs.class).customTerminalCommand().getValue();
if (custom == null || custom.trim().isEmpty()) {
return;
}
var format = custom.contains("$cmd") ? custom : custom + " $cmd";
try (var pc = ShellStore.local().create().start()) {
var toExecute = format.replace("$cmd", command);
if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + name + "\" " + toExecute;
} else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
}
pc.executeSimpleCommand(toExecute);
}
}
@Override
public boolean isSelectable() {
return true;
}
@Override
public boolean isAvailable() {
return true;
}
}
static class ITerm2Type extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
public ITerm2Type() {
super("proc.iterm2", "iTerm2");
}
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
var cmd = String.format(
"""
osascript - "$@" <<EOF
if application "iTerm" is running then
tell application "iTerm"
create window with profile "Default" command "%s"
end tell
else
activate application "iTerm"
delay 1
tell application "iTerm"
tell current window
tell current session
write text "%s"
end tell
end tell
end tell
end if
EOF""",
command.replaceAll("\"", "\\\\\""), command.replaceAll("\"", "\\\\\""));
pc.executeSimpleCommand(cmd);
}
}
}
static class WarpType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
public WarpType() {
super("proc.warp", "Warp");
}
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
var cmd = String.format(
"""
osascript - "$@" <<EOF
tell application "Warp" to activate
tell application "System Events" to tell process "Warp" to keystroke "t" using command down
delay 1
tell application "System Events"
tell process "Warp"
keystroke "%s"
key code 36
end tell
end tell
EOF
""",
command.replaceAll("\"", "\\\\\""));
pc.executeSimpleCommand(cmd);
}
}
}
public abstract static class SimpleType extends ExternalApplicationType.PathApplication
implements ExternalTerminalType {
private final String displayName;
public SimpleType(String id, String executable, String displayName) {
super(id, executable);
this.displayName = displayName;
}
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
ApplicationHelper.checkSupport(pc, executable, displayName);
var toExecute = executable + " " + toCommand(name, command);
if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + name + "\" " + toExecute;
} else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
}
pc.executeSimpleCommand(toExecute);
}
}
protected abstract String toCommand(String name, String command);
public boolean isAvailable() {
try (ShellProcessControl pc = ShellStore.local().create().start()) {
return pc.executeBooleanSimpleCommand(pc.getShellType().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
return false;
}
}
@Override
public boolean isSelectable() {
return true;
}
}
}

View file

@ -0,0 +1,87 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.extension.util.ScriptHelper;
import lombok.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
public class LocalCommandControlImpl extends CommandControlImpl {
private Process process;
public LocalCommandControlImpl(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand
) {
super(parent, command, terminalCommand);
}
@Override
public boolean waitFor() {
try {
return process.waitFor(exitTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
@Override
public String prepareTerminalOpen() throws Exception {
try (var ignored = parent.start()) {
var operator = parent.getShellType().getConcatenationOperator();
var consoleCommand = terminalCommand.apply(parent)
+ operator
+ parent.getShellType().getPauseCommand();
return parent.prepareIntermediateTerminalOpen(consoleCommand);
}
}
@Override
public void closeStdin() throws IOException {
process.getOutputStream().close();
}
@Override
public boolean isStdinClosed() {
return false;
}
@Override
public void close() throws IOException {
waitFor();
}
@Override
public void kill() throws Exception {
process.destroyForcibly();
}
@Override
public CommandProcessControl start() throws Exception {
var file = ScriptHelper.createLocalExecScript(command.apply(parent));
process = new ProcessBuilder(parent.getShellType().executeCommandListWithShell(file)).start();
return this;
}
@Override
public InputStream getStdout() {
return process.getInputStream();
}
@Override
public OutputStream getStdin() {
return process.getOutputStream();
}
@Override
public InputStream getStderr() {
return process.getErrorStream();
}
}

View file

@ -0,0 +1,187 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.*;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ScriptHelper;
import org.apache.commons.exec.CommandLine;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
public class LocalShellControlImpl extends ShellControlImpl {
private static final int EXIT_TIMEOUT = 5000;
protected boolean stdinClosed;
private Process process;
@Override
public CommandProcessControl command(
Function<ShellProcessControl, String> command, Function<ShellProcessControl, String> terminalCommand) {
var control = ProcessControlProvider.createCommand(this, command, terminalCommand);
if (control != null) {
return control;
}
return new LocalCommandControlImpl(this, command, terminalCommand);
}
@Override
public String prepareTerminalOpen() throws Exception {
return prepareIntermediateTerminalOpen(null);
}
public void closeStdin() throws IOException {
if (stdinClosed) {
return;
}
stdinClosed = true;
getStdin().close();
}
@Override
public boolean isStdinClosed() {
return stdinClosed;
}
@Override
public ShellType getShellType() {
return ShellTypes.getPlatformDefault();
}
@Override
public void close() throws IOException {
TrackEvent.withTrace("proc", "Closing local shell ...").handle();
exitAndWait();
}
@Override
public void exitAndWait() throws IOException {
if (!running) {
return;
}
if (!isStdinClosed()) {
writeLine(shellType.getExitCommand());
}
getStdout().close();
getStderr().close();
getStdin().close();
stdinClosed = true;
uuid = null;
if (!PrefsProvider.get(ProcPrefs.class).enableCaching().get()) {
shellType = null;
charset = null;
command = null;
tempDirectory = null;
}
try {
process.waitFor(EXIT_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {
}
running = false;
}
@Override
public void kill() throws IOException {
TrackEvent.withTrace("proc", "Killing local shell ...").handle();
process.destroyForcibly();
// Don't close stout as that might hang too in case it is frozen
// getStdout().close();
// getStderr().close();
getStdin().close();
running = false;
}
public void restart() throws Exception {
close();
start();
}
@Override
public String prepareIntermediateTerminalOpen(String content) throws Exception {
try (var pc = start()) {
var initCommand = ScriptHelper.constructOpenWithInitScriptCommand(pc, initCommands, content);
TrackEvent.withDebug("proc", "Writing open init script")
.tag("initCommand", initCommand)
.tag("content", content)
.handle();
return initCommand;
}
}
@Override
public boolean isLocal() {
return true;
}
@Override
public ShellProcessControl elevated(Predicate<ShellProcessControl> elevationFunction) {
throw new UnsupportedOperationException();
}
@Override
public ShellProcessControl start() throws Exception {
if (running) {
return this;
}
var localType = ShellTypes.getPlatformDefault();
command = localType.getNormalOpenCommand();
uuid = UUID.randomUUID();
TrackEvent.withTrace("proc", "Starting local process")
.tag("command", command)
.handle();
var parsed = CommandLine.parse(command);
var args = new ArrayList<String>();
args.add(parsed.getExecutable());
args.addAll(List.of(parsed.getArguments()));
process = Runtime.getRuntime().exec(args.toArray(String[]::new));
stdinClosed = false;
running = true;
shellType = localType;
if (charset == null) {
charset = shellType.determineCharset(this);
}
osType = OsType.getLocal();
for (String s : initCommands) {
executeLine(s);
}
onInit.accept(this);
return this;
}
@Override
public InputStream getStdout() {
return process.getInputStream();
}
@Override
public OutputStream getStdin() {
return process.getOutputStream();
}
@Override
public InputStream getStderr() {
return process.getErrorStream();
}
}

View file

@ -0,0 +1,71 @@
package io.xpipe.ext.proc;
import com.dlsc.formsfx.model.structure.Field;
import com.dlsc.formsfx.model.structure.SingleSelectionField;
import com.dlsc.formsfx.model.structure.StringField;
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleTextControl;
import com.dlsc.preferencesfx.model.Setting;
import com.dlsc.preferencesfx.util.VisibilityProperty;
import io.xpipe.app.prefs.TranslatableComboBoxControl;
import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.prefs.PrefsHandler;
import io.xpipe.extension.prefs.PrefsProvider;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import java.util.List;
public class ProcPrefs extends PrefsProvider {
private final BooleanProperty enableCaching = new SimpleBooleanProperty(true);
public ObservableBooleanValue enableCaching() {
return enableCaching;
}
private final ObjectProperty<ExternalTerminalType> terminalType = new SimpleObjectProperty<>();
private final SimpleListProperty<ExternalTerminalType> terminalTypeList = new SimpleListProperty<>(
FXCollections.observableArrayList(PrefsChoiceValue.getSupported(ExternalTerminalType.class)));
private final SingleSelectionField<ExternalTerminalType> terminalTypeControl = Field.ofSingleSelectionType(
terminalTypeList, terminalType)
.render(() -> new TranslatableComboBoxControl<>());
// Custom terminal
// ===============
private final StringProperty customTerminalCommand = new SimpleStringProperty("");
private final StringField customTerminalCommandControl = editable(
StringField.ofStringType(customTerminalCommand).render(() -> new SimpleTextControl()),
terminalType.isEqualTo(ExternalTerminalType.CUSTOM));
public ObservableValue<ExternalTerminalType> terminalType() {
return terminalType;
}
public ObservableValue<String> customTerminalCommand() {
return customTerminalCommand;
}
@Override
public void addPrefs(PrefsHandler handler) {
handler.addSetting(
List.of("integrations"),
"proc.terminal",
Setting.of("app.defaultProgram", terminalTypeControl, terminalType),
ExternalTerminalType.class);
handler.addSetting(
List.of("integrations"),
"proc.terminal",
Setting.of("proc.customTerminalCommand", customTerminalCommandControl, customTerminalCommand)
.applyVisibility(VisibilityProperty.of(terminalType.isEqualTo(ExternalTerminalType.CUSTOM))),
String.class);
}
@Override
public void init() {
if (terminalType.get() == null) {
terminalType.set(ExternalTerminalType.getDefault());
}
}
}

View file

@ -0,0 +1,38 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellProcessControl;
import lombok.NonNull;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ProcProvider extends ProcessControlProvider {
@Override
public ShellProcessControl sub(
ShellProcessControl parent, @NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand
) {
return null;
}
@Override
public CommandProcessControl command(
ShellProcessControl parent, @NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand
) {
return null;
}
@Override
public ShellProcessControl createLocalProcessControl() {
return new LocalShellControlImpl();
}
@Override
public ShellProcessControl createSshControl(Object sshStore) {
return null;
}
}

View file

@ -0,0 +1,47 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.ProcessControl;
import io.xpipe.extension.event.TrackEvent;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public abstract class ProcessControlImpl implements ProcessControl {
protected Charset charset;
protected boolean running;
protected boolean sensitive;
@Override
public boolean isRunning() {
return running;
}
@Override
public void writeLine(String line) throws IOException {
if (isStdinClosed()) {
throw new IllegalStateException("Input is closed");
}
// Censor actual written line to prevent leaking sensitive information
TrackEvent.withTrace("proc", "Writing line").tag("line", line).handle();
getStdin().write((line + "\n").getBytes(getCharset()));
getStdin().flush();
}
@Override
public void write(byte[] b) throws IOException {
if (isStdinClosed()) {
throw new IllegalStateException("Input is closed");
}
getStdin().write(b);
getStdin().flush();
}
@Override
public final Charset getCharset() {
return charset != null ? charset : StandardCharsets.US_ASCII;
}
}

View file

@ -0,0 +1,118 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.util.SecretValue;
import io.xpipe.core.util.XPipeTempDirectory;
import lombok.Getter;
import lombok.NonNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class ShellControlImpl extends ProcessControlImpl implements ShellProcessControl {
protected Integer startTimeout = 10000;
protected UUID uuid;
protected String command;
protected List<String> initCommands = new ArrayList<>();
protected String tempDirectory;
protected Consumer<ShellProcessControl> onInit = processControl -> {};
@Override
public void onInit(Consumer<ShellProcessControl> pc) {
this.onInit = pc;
}
@Getter
protected ShellType shellType;
@Getter
protected OsType osType;
@Getter
protected SecretValue elevationPassword;
@Override
public String getTemporaryDirectory() throws Exception {
if (tempDirectory == null) {
checkRunning();
tempDirectory = XPipeTempDirectory.get(this);
}
return tempDirectory;
}
@Override
public void checkRunning() throws Exception {
if (!isRunning()) {
throw new IllegalStateException("Shell process control is not running");
}
}
@Override
public ShellProcessControl sensitive() {
this.sensitive = true;
return this;
}
@Override
public ShellProcessControl elevation(SecretValue value) {
this.elevationPassword = value;
return this;
}
@Override
public ShellProcessControl initWith(List<String> cmds) {
this.initCommands.addAll(cmds);
return this;
}
@Override
public ShellProcessControl subShell(
@NonNull Function<ShellProcessControl, String> command,
BiFunction<ShellProcessControl, String, String> terminalCommand) {
return ProcessControlProvider.createSub(this, command, terminalCommand);
}
@Override
public CommandProcessControl command(Function<ShellProcessControl, String> command) {
return command(command, command);
}
@Override
public CommandProcessControl command(
Function<ShellProcessControl, String> command, Function<ShellProcessControl, String> terminalCommand) {
var control = ProcessControlProvider.createCommand(this, command, terminalCommand);
return control;
}
@Override
public void executeLine(String command) throws Exception {
writeLine(command);
if (getShellType().doesRepeatInput()) {
while (true) {
int c = getStdout().read();
if (c == -1) {
break;
}
if (c == '\n') {
break;
}
}
}
}
@Override
public abstract void exitAndWait() throws IOException;
}

View file

@ -0,0 +1,13 @@
package io.xpipe.ext.proc;
import io.xpipe.app.util.TerminalProvider;
import io.xpipe.extension.prefs.PrefsProvider;
public class TerminalProviderImpl extends TerminalProvider {
@Override
public void openInTerminal(String title, String command) throws Exception {
var type = PrefsProvider.get(ProcPrefs.class).terminalType().getValue();
type.launch(title, command);
}
}

View file

@ -0,0 +1,64 @@
package io.xpipe.ext.proc.action;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.ProxyManagerProvider;
import io.xpipe.extension.I18n;
import io.xpipe.extension.util.ActionProvider;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class InstallConnectorAction implements ActionProvider {
@Value
static class Action implements ActionProvider.Action {
DataStoreEntry entry;
@Override
public boolean requiresPlatform() {
return true;
}
@Override
public void execute() throws Exception {
try (ShellProcessControl s = ((ShellStore) entry.getStore()).create().start()) {
ProxyManagerProvider.get().setup(s);
}
}
}
@Override
public DataStoreCallSite<?> getDataStoreCallSite() {
return new DataStoreCallSite<ShellStore>() {
@Override
public LaunchShortcutAction.Action createAction(ShellStore store) {
return new LaunchShortcutAction.Action(DataStorage.get().getStore(store));
}
@Override
public Class<ShellStore> getApplicableClass() {
return ShellStore.class;
}
@Override
public boolean isApplicable(ShellStore o) throws Exception {
return !ShellStore.isLocal(o);
}
@Override
public ObservableValue<String> getName(ShellStore store) {
return I18n.observable("installConnector");
}
@Override
public String getIcon(ShellStore store) {
return "mdi2c-code-greater-than";
}
};
}
}

View file

@ -0,0 +1,89 @@
package io.xpipe.ext.proc.action;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.TerminalProvider;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.I18n;
import io.xpipe.extension.util.ActionProvider;
import javafx.beans.value.ObservableValue;
import lombok.Value;
import java.util.List;
import java.util.UUID;
public class LaunchAction implements ActionProvider {
@Value
static class Action implements ActionProvider.Action {
DataStoreEntry entry;
@Override
public boolean requiresPlatform() {
return false;
}
@Override
public void execute() throws Exception {
var storeName = entry.getName();
if (entry.getStore() instanceof ShellStore s) {
String command = s.create().prepareTerminalOpen();
TerminalProvider.open(storeName, command);
}
}
}
@Override
public LauncherCallSite getLauncherCallSite() {
return new LauncherCallSite() {
@Override
public String getId() {
return "launch";
}
@Override
public ActionProvider.Action createAction(List<String> args) {
var entry = DataStorage.get().getStoreEntryByUuid(UUID.fromString(args.get(1))).orElseThrow();
return new Action(entry);
}
};
}
@Override
public DataStoreCallSite<?> getDataStoreCallSite() {
return new DataStoreCallSite<DataStore>() {
@Override
public boolean isApplicable(DataStore o) throws Exception {
return o instanceof ShellStore;
}
@Override
public ObservableValue<String> getName(DataStore store) {
return I18n.observable("openShell");
}
@Override
public String getIcon(DataStore store) {
return "mdi2c-code-greater-than";
}
@Override
public ActionProvider.Action createAction(DataStore store) {
return new Action(DataStorage.get().getEntryByStore(store).orElseThrow());
}
@Override
public Class<DataStore> getApplicableClass() {
return DataStore.class;
}
@Override
public boolean isMajor() {
return true;
}
};
}
}

View file

@ -0,0 +1,67 @@
package io.xpipe.ext.proc.action;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.I18n;
import io.xpipe.extension.util.ActionProvider;
import io.xpipe.extension.util.DesktopShortcuts;
import io.xpipe.extension.util.XPipeDistributionType;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class LaunchShortcutAction implements ActionProvider {
@Value
static class Action implements ActionProvider.Action {
DataStoreEntry entry;
@Override
public boolean requiresPlatform() {
return false;
}
@Override
public void execute() throws Exception {
DesktopShortcuts.create("xpipe://launch/" + entry.getUuid().toString(), entry.getName());
}
}
@Override
public boolean isActive() throws Exception {
return XPipeDistributionType.get().supportsURLs();
}
@Override
public DataStoreCallSite<?> getDataStoreCallSite() {
return new DataStoreCallSite<ShellStore>() {
@Override
public Action createAction(ShellStore store) {
return new Action(DataStorage.get().getStore(store));
}
@Override
public Class<ShellStore> getApplicableClass() {
return ShellStore.class;
}
@Override
public ObservableValue<String> getName(ShellStore store) {
return I18n.observable("createShortcut");
}
@Override
public String getIcon(ShellStore store) {
return "mdi2c-code-greater-than";
}
@Override
public boolean isMajor() {
return false;
}
};
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.ext.proc.augment;
import java.util.List;
public class CmdCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return executable.equals("cmd");
}
@Override
protected void prepareBaseCommand(List<String> baseCommand) {
remove(baseCommand, "/C");
}
@Override
protected void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand) {
if (hasSubCommand) {
baseCommand.add("/C");
}
}
@Override
protected void modifyNonTerminalCommand(List<String> baseCommand) {}
}

View file

@ -0,0 +1,70 @@
package io.xpipe.ext.proc.augment;
import io.xpipe.core.process.ShellProcessControl;
import org.apache.commons.exec.CommandLine;
import java.util.*;
import java.util.stream.Collectors;
public abstract class CommandAugmentation {
private static final Set<CommandAugmentation> ALL = new HashSet<>();
public static CommandAugmentation get(String cmd) {
var parsed = CommandLine.parse(cmd);
var executable = parsed.getExecutable().toLowerCase(Locale.ROOT).replaceAll("\\.exe$", "");
if (ALL.isEmpty()) {
ALL.addAll(
ServiceLoader.load(CommandAugmentation.class.getModule().getLayer(), CommandAugmentation.class)
.stream()
.map(commandAugmentationProvider -> commandAugmentationProvider.get())
.collect(Collectors.toSet()));
}
return ALL.stream()
.filter(commandAugmentation -> commandAugmentation.matches(executable))
.findFirst()
.orElse(new NoCommandAugmentation());
}
private static List<String> split(String cmd) {
var parsed = CommandLine.parse(cmd);
var splitCommand = new ArrayList<>(Arrays.asList(parsed.getArguments()));
splitCommand.add(0, parsed.getExecutable().replaceAll("\\.exe$", ""));
return splitCommand;
}
public abstract boolean matches(String executable);
protected void addIfNeeded(List<String> baseCommand, String arg) {
if (!baseCommand.contains(arg)) {
baseCommand.add(1, arg);
}
}
protected void remove(List<String> baseCommand, String... args) {
for (var arg : args) {
baseCommand.removeIf(s -> s.toLowerCase(Locale.ROOT).equals(arg));
}
}
public String prepareTerminalCommand(ShellProcessControl proc, String cmd, String subCommand) {
var split = split(cmd);
prepareBaseCommand(split);
modifyTerminalCommand(split, subCommand != null);
return proc.getShellType().flatten(split) + (subCommand != null ? " " + subCommand : "");
}
public String prepareNonTerminalCommand(ShellProcessControl proc, String cmd) {
var split = split(cmd);
prepareBaseCommand(split);
modifyNonTerminalCommand(split);
return proc.getShellType().flatten(split);
}
protected abstract void prepareBaseCommand(List<String> baseCommand);
protected abstract void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand);
protected abstract void modifyNonTerminalCommand(List<String> baseCommand);
}

View file

@ -0,0 +1,20 @@
package io.xpipe.ext.proc.augment;
import java.util.List;
public class NoCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return true;
}
@Override
protected void prepareBaseCommand(List<String> baseCommand) {}
@Override
protected void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand) {}
@Override
protected void modifyNonTerminalCommand(List<String> baseCommand) {}
}

View file

@ -0,0 +1,29 @@
package io.xpipe.ext.proc.augment;
import java.util.List;
public class PosixShellCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return executable.equals("sh") || executable.equals("bash") || executable.equals("zsh");
}
@Override
protected void prepareBaseCommand(List<String> baseCommand) {
remove(baseCommand, "-l", "--login");
remove(baseCommand, "-i");
remove(baseCommand, "-c");
remove(baseCommand, "-s");
}
@Override
protected void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand) {
addIfNeeded(baseCommand, "-i");
if (hasSubCommand) {
baseCommand.add("-c");
}
}
@Override
protected void modifyNonTerminalCommand(List<String> baseCommand) {}
}

View file

@ -0,0 +1,28 @@
package io.xpipe.ext.proc.augment;
import java.util.List;
public class PowershellCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return executable.equals("powershell") || executable.equals("pwsh");
}
@Override
protected void prepareBaseCommand(List<String> baseCommand) {
remove(baseCommand, "-Command");
remove(baseCommand, "-NonInteractive");
}
@Override
protected void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand) {
if (hasSubCommand) {
baseCommand.add("-Command");
}
}
@Override
protected void modifyNonTerminalCommand(List<String> baseCommand) {
addIfNeeded(baseCommand, "-NonInteractive");
}
}

View file

@ -0,0 +1,32 @@
package io.xpipe.ext.proc.augment;
import java.util.List;
public class SshCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return executable.equals("ssh");
}
@Override
protected void prepareBaseCommand(List<String> baseCommand) {
baseCommand.removeIf(s -> s.equals("-T"));
baseCommand.removeIf(s -> s.equals("-t"));
baseCommand.removeIf(s -> s.equals("-tt"));
baseCommand.removeIf(s -> s.equals("-oStrictHostKeyChecking=yes"));
addIfNeeded(baseCommand, "-oStrictHostKeyChecking=no");
// addIfNeeded(baseCommand,"-oPasswordAuthentication=no");
}
@Override
protected void modifyTerminalCommand(List<String> baseCommand, boolean hasSubCommand) {
addIfNeeded(baseCommand, "-t");
}
@Override
protected void modifyNonTerminalCommand(List<String> baseCommand) {
addIfNeeded(baseCommand, "-T");
}
}

View file

@ -0,0 +1,89 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.CommandExecutionStore;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.proc.augment.CommandAugmentation;
import io.xpipe.extension.util.Validators;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.InputStream;
import java.io.OutputStream;
@JsonTypeName("cmd")
@SuperBuilder
@Jacksonized
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class CommandStore extends JacksonizedValue implements StreamDataStore, CommandExecutionStore {
String cmd;
ShellStore host;
ShellType shell;
DataFlow flow;
boolean requiresElevation;
public DataFlow getFlow() {
return flow;
}
@Override
public void validate() throws Exception {
host.validate();
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(cmd, "Command");
Validators.nonNull(host, "Host");
host.checkComplete();
Validators.nonNull(flow, "Flow");
}
@Override
public InputStream openInput() throws Exception {
if (!flow.hasInput()) {
throw new UnsupportedOperationException();
}
var cmd = create().start();
return cmd.getStdout();
}
@Override
public OutputStream openOutput() throws Exception {
if (!flow.hasOutput()) {
throw new UnsupportedOperationException();
}
var cmd = create().start();
return cmd.getStdin();
}
@Override
public CommandProcessControl create() throws Exception {
var augmentation = CommandAugmentation.get(getCmd());
var base = shell != null ? host.create().subShell(shell) : host.create();
var command = base.command(
proc -> augmentation.prepareNonTerminalCommand(proc, getCmd()),
proc -> augmentation.prepareTerminalCommand(proc, getCmd(), null))
.complex();
if (requiresElevation) {
command = command.elevated();
}
return command;
}
}

View file

@ -0,0 +1,159 @@
package io.xpipe.ext.proc.store;
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.process.ShellTypes;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.fxcomps.impl.DataStoreFlowChoiceComp;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DataStoreFormatter;
import io.xpipe.extension.util.DialogHelper;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.util.Arrays;
import java.util.List;
public class CommandStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
CommandStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;
}
@Override
public String getId() {
return "cmd";
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
CommandStore st = (CommandStore) store.getValue();
Property<ShellStore> machineProperty =
new SimpleObjectProperty<>(st.getHost() != null ? st.getHost() : new LocalStore());
ShellType type;
try {
type = machineProperty.getValue().determineType();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
type = null;
}
Property<ShellType> shellTypeProperty =
new SimpleObjectProperty<>(st.getShell() != null ? st.getShell() : type);
Property<String> commandProp = new SimpleObjectProperty<>(st.getCmd());
Property<DataFlow> flowProperty = new SimpleObjectProperty<>(st.getFlow());
var requiresElevationProperty = new SimpleBooleanProperty(st.isRequiresElevation());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(I18n.observable("proc.host"), ShellStoreChoiceComp.host(machineProperty), machineProperty)
.nonNull(val)
.addComp(
I18n.observable("proc.shellType"),
new ShellTypeChoiceComp(shellTypeProperty),
shellTypeProperty)
.addComp(
I18n.observable("proc.command"),
new IntegratedTextAreaComp(commandProp, false, "command", "txt"),
commandProp)
.nonNull(val)
.addComp("proc.usage", new DataStoreFlowChoiceComp(flowProperty, DataFlow.values()), flowProperty)
.addToggle("requiresElevation", requiresElevationProperty)
.bind(
() -> {
return CommandStore.builder()
.cmd(commandProp.getValue())
.host(machineProperty.getValue())
.shell(shellTypeProperty.getValue())
.flow(flowProperty.getValue())
.requiresElevation(requiresElevationProperty.get())
.build();
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public String toSummaryString(DataStore store, int length) {
CommandStore s = store.asNeeded();
return DataStoreFormatter.formatSubHost(
l -> DataStoreFormatter.cut(s.getCmd().lines().findFirst().orElse("?"), l), s.getHost(), length);
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
CommandStore s = store.asNeeded();
var shellName = s.getShell() != null ? s.getShell().getDisplayName() : "Default Shell";
return String.format("%s Command", shellName);
}
@Override
public Category getCategory() {
return Category.STREAM;
}
@Override
public DataStore defaultStore() {
return CommandStore.builder()
.host(new LocalStore())
.flow(DataFlow.INPUT)
.build();
}
@Override
public List<String> getPossibleNames() {
return List.of("cmd", "command", "shell", "run", "execute");
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(CommandStore.class);
}
@Override
public Dialog dialogForStore(DataStore store) {
CommandStore commandStore = store.asNeeded();
var cmdQ = Dialog.query("Command", true, true, false, commandStore.getCmd(), QueryConverter.STRING);
var machineQ = DialogHelper.shellQuery("Command Host", commandStore.getHost());
var shellQuery = Dialog.lazy(() -> {
var available = Arrays.stream(ShellTypes.getAllShellTypes()).toList();
return Dialog.choice(
"Shell Type",
t -> t.getDisplayName(),
true,
false,
available.get(0),
available.toArray(ShellType[]::new));
});
var flowQuery = DialogHelper.dataStoreFlowQuery(commandStore.getFlow(), DataFlow.values());
return Dialog.chain(cmdQ, machineQ, shellQuery, flowQuery).evaluateTo(() -> {
return CommandStore.builder()
.cmd(cmdQ.getResult())
.host(machineQ.getResult())
.shell(shellQuery.getResult())
.flow(flowQuery.getResult())
.build();
});
}
}

View file

@ -0,0 +1,69 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellTypes;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.extension.util.Validators;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
@JsonTypeName("docker")
@SuperBuilder
@Jacksonized
@Getter
public class DockerStore extends JacksonizedValue implements MachineStore {
private final ShellStore host;
private final String containerName;
public DockerStore(ShellStore host, String containerName) {
this.host = host;
this.containerName = containerName;
}
public static boolean isSupported(ShellStore host) {
return host.create().command("docker --help").startAndCheckExit();
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(host, "Host");
host.checkComplete();
Validators.nonNull(containerName, "Name");
}
@Override
public void validate() throws Exception {
host.validate();
Validators.hostFeature(host, DockerStore::isSupported, "docker");
try (var pc = host.create()
.command(List.of("docker", "container", "inspect", containerName))
.elevated()
.start()) {
pc.discardOrThrow();
}
}
@Override
public ShellProcessControl createControl() {
return host.create()
.subShell(
shellProcessControl ->
"docker exec -i " + containerName + " " + ShellTypes.BASH.getNormalOpenCommand(),
(shellProcessControl, s) -> {
if (s != null) {
return "docker exec -it " + containerName + " " + s;
} else {
return "docker exec -it " + containerName;
}
})
.elevated(shellProcessControl -> true);
}
}

View file

@ -0,0 +1,98 @@
package io.xpipe.ext.proc.store;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DataStoreFormatter;
import io.xpipe.extension.util.DialogHelper;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class DockerStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
DockerStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
DockerStore st = (DockerStore) store.getValue();
Property<ShellStore> shellProp =
new SimpleObjectProperty<>(st.getHost() != null ? st.getHost() : new LocalStore());
Property<String> containerProp = new SimpleObjectProperty<>(st.getContainerName());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(
I18n.observable("host"),
ShellStoreChoiceComp.host(st, shellProp),
shellProp)
.nonNull(val)
.addString(I18n.observable("proc.container"), containerProp)
.nonNull(val)
.bind(
() -> {
return new DockerStore(shellProp.getValue(), containerProp.getValue());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
DockerStore s = store.asNeeded();
return String.format("%s %s", s.queryMachineName(), I18n.get("proc.container"));
}
@Override
public String toSummaryString(DataStore store, int length) {
DockerStore s = store.asNeeded();
return DataStoreFormatter.formatSubHost(
l -> DataStoreFormatter.cut(s.getContainerName(), l), s.getHost(), length);
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(DockerStore.class);
}
@Override
public DataStore defaultStore() {
return new DockerStore(new LocalStore(), null);
}
@Override
public Dialog dialogForStore(DataStore store) {
DockerStore dockerStore = store.asNeeded();
var hostQ = DialogHelper.machineQuery(dockerStore.getHost());
var containerQ =
Dialog.query("Container", false, true, false, dockerStore.getContainerName(), QueryConverter.STRING);
return Dialog.chain(hostQ, containerQ).evaluateTo(() -> {
return new DockerStore(hostQ.getResult(), containerQ.getResult());
});
}
@Override
public List<String> getPossibleNames() {
return List.of("docker", "docker_container");
}
}

View file

@ -0,0 +1,51 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.proc.augment.CommandAugmentation;
import io.xpipe.extension.util.Validators;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("shellCommand")
public class ShellCommandStore extends JacksonizedValue implements MachineStore {
private final String cmd;
private final ShellStore host;
public ShellCommandStore(String cmd, ShellStore host) {
this.cmd = cmd;
this.host = host;
}
public static ShellCommandStore shell(ShellStore host, ShellType type) {
return ShellCommandStore.builder()
.host(host)
.cmd(type.getNormalOpenCommand())
.build();
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(cmd, "Command");
Validators.nonNull(host, "Host");
Validators.namedStoreExists(host, "Host");
host.checkComplete();
}
@Override
public ShellProcessControl createControl() {
var augmentation = CommandAugmentation.get(getCmd());
return host.create()
.subShell(
proc -> augmentation.prepareNonTerminalCommand(proc, getCmd()),
(proc, s) -> augmentation.prepareTerminalCommand(proc, getCmd(), s));
}
}

View file

@ -0,0 +1,113 @@
package io.xpipe.ext.proc.store;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.DataStoreProviders;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DataStoreFormatter;
import io.xpipe.extension.util.DialogHelper;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class ShellCommandStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
ShellCommandStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;
}
@Override
public String getId() {
return "shellCommand";
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
ShellCommandStore st = (ShellCommandStore) store.getValue();
Property<ShellStore> hostProperty = new SimpleObjectProperty<>(st.getHost());
Property<String> commandProp = new SimpleObjectProperty<>(st.getCmd());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(
I18n.observable("host"),
ShellStoreChoiceComp.host(st, hostProperty),
hostProperty)
.nonNull(val)
.addString(I18n.observable("proc.command"), commandProp)
.nonNull(val)
.bind(
() -> {
return new ShellCommandStore(commandProp.getValue(), hostProperty.getValue());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
ShellCommandStore s = store.asNeeded();
return s.queryMachineName();
}
@Override
public String toSummaryString(DataStore store, int length) {
ShellCommandStore s = store.asNeeded();
var local = ShellStore.isLocal(s.getHost());
if (local) {
return DataStoreFormatter.cut(s.getCmd(), length);
} else {
var machineString = DataStoreProviders.byStore(s.getHost()).toSummaryString(s.getHost(), length / 2);
var fileString = DataStoreFormatter.cut(s.getCmd().toString(), length - machineString.length() - 3);
return String.format("%s @ %s", fileString, machineString);
}
}
@Override
public Category getCategory() {
return Category.SHELL;
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(ShellCommandStore.class);
}
@Override
public DataStore defaultStore() {
return new ShellCommandStore(null, new LocalStore());
}
@Override
public List<String> getPossibleNames() {
return List.of("shell_command");
}
@Override
public Dialog dialogForStore(DataStore store) {
ShellCommandStore s = store.asNeeded();
var hostQ = DialogHelper.shellQuery("Command Host", s.getHost());
var commandQ = Dialog.query("Command", true, true, false, s.getCmd(), QueryConverter.STRING);
return Dialog.chain(hostQ, commandQ).evaluateTo(() -> {
return new ShellCommandStore(commandQ.getResult(), hostQ.getResult());
});
}
}

View file

@ -0,0 +1,54 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.extension.util.Validators;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("shellEnvironment")
public class ShellEnvironmentStore extends JacksonizedValue implements MachineStore {
private final String commands;
private final ShellStore host;
private final ShellType shell;
public ShellEnvironmentStore(String commands, ShellStore host, ShellType shell) {
this.commands = commands;
this.host = host;
this.shell = shell;
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(commands, "Commands");
Validators.nonNull(host, "Host");
Validators.namedStoreExists(host, "Host");
host.checkComplete();
}
@Override
public void validate() throws Exception {
try (var ignored = create().start()) {
}
}
@Override
public ShellProcessControl createControl() {
var pc = host.create();
if (shell != null) {
pc = pc.subShell(shell);
}
return pc.initWith(commands.lines().toList());
}
}

View file

@ -0,0 +1,102 @@
package io.xpipe.ext.proc.store;
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.Identifiers;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class ShellEnvironmentStoreProvider implements DataStoreProvider {
@Override
public boolean isShareable() {
return true;
}
@Override
public String getId() {
return "shellEnvironment";
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
ShellEnvironmentStore st = store.getValue().asNeeded();
Property<ShellStore> hostProperty = new SimpleObjectProperty<>(st.getHost());
Property<String> commandProp = new SimpleObjectProperty<>(st.getCommands());
Property<ShellType> shellTypeProperty = new SimpleObjectProperty<>(st.getShell());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(I18n.observable("host"), ShellStoreChoiceComp.host(st, hostProperty), hostProperty)
.nonNull(val)
.addComp(
I18n.observable("proc.shellType"),
new ShellTypeChoiceComp(shellTypeProperty),
shellTypeProperty)
.addComp(
I18n.observable("proc.commands"),
new IntegratedTextAreaComp(commandProp, false, "commands", "txt"),
commandProp)
.nonNull(val)
.bind(
() -> {
return new ShellEnvironmentStore(
commandProp.getValue(), hostProperty.getValue(), shellTypeProperty.getValue());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
ShellEnvironmentStore s = store.asNeeded();
var name = s.getShell() != null ? s.getShell().getDisplayName() : "Default";
return I18n.get("shellEnvironment.informationFormat", name);
}
@Override
public String toSummaryString(DataStore store, int length) {
ShellEnvironmentStore s = store.asNeeded();
var commandSummary = "<" + s.getCommands().lines().count() + " commands>";
return commandSummary;
}
@Override
public DataStore getParent(DataStore store) {
ShellEnvironmentStore s = store.asNeeded();
return s.getHost();
}
@Override
public Category getCategory() {
return Category.SHELL;
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(ShellEnvironmentStore.class);
}
@Override
public DataStore defaultStore() {
return new ShellEnvironmentStore(null, new LocalStore(), null);
}
@Override
public List<String> getPossibleNames() {
return Identifiers.get("shell", "environment");
}
}

View file

@ -0,0 +1,66 @@
package io.xpipe.ext.proc.store;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.process.ShellTypes;
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.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import java.util.Arrays;
import java.util.Map;
@AllArgsConstructor
public class ShellTypeChoiceComp extends SimpleComp {
public static final Map<ShellType, String> ICONS = Map.of(
ShellTypes.CMD, "cmd.png",
ShellTypes.POWERSHELL, "powershell.png",
ShellTypes.ZSH, "cmd.png",
ShellTypes.SH, "cmd.png",
ShellTypes.BASH, "cmd.png");
private final Property<ShellType> selected;
private Region createGraphic(ShellType s) {
if (s == null) {
return createEmptyGraphic();
}
var img = XPipeDaemon.getInstance().image("base:" + ICONS.get(s));
var imgView = new ImageView(img);
imgView.setFitWidth(16);
imgView.setFitHeight(16);
var name = s.getDisplayName();
return new Label(name, imgView);
}
private Region createEmptyGraphic() {
var img = XPipeDaemon.getInstance().image("proc:defaultShell_icon.png");
var imgView = new ImageView(img);
imgView.setFitWidth(16);
imgView.setFitHeight(16);
return new Label(I18n.get("default"), imgView);
}
@Override
protected Region createSimple() {
var comboBox = new CustomComboBoxBuilder<>(selected, this::createGraphic, null, e -> true);
comboBox.add(null);
Arrays.stream(ShellTypes.getAllShellTypes()).forEach(shellType -> comboBox.add(shellType));
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("choice-comp");
cb.setMaxWidth(2000);
return cb;
}
}

View file

@ -0,0 +1,64 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.SecretValue;
import io.xpipe.extension.util.Validators;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Jacksonized
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@JsonTypeName("ssh")
public class SshStore extends JacksonizedValue implements MachineStore {
ShellStore proxy;
String host;
Integer port;
String user;
SecretValue password;
SshKey key;
public SshStore(ShellStore proxy, String host, Integer port, String user, SecretValue password, SshKey key) {
this.proxy = proxy;
this.host = host;
this.port = port;
this.user = user;
this.password = password;
this.key = key;
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(proxy, "Proxy");
Validators.nonNull(host, "Host");
Validators.nonNull(port, "Port");
Validators.nonNull(user, "User");
proxy.checkComplete();
}
@Override
public ShellProcessControl createControl() {
return ProcessControlProvider.createSsh(this);
}
@SuperBuilder
@Jacksonized
@Getter
public static class SshKey {
@NonNull
String file;
SecretValue password;
}
}

View file

@ -0,0 +1,162 @@
package io.xpipe.ext.proc.store;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.SecretValue;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.impl.FileStoreChoiceComp;
import io.xpipe.extension.fxcomps.impl.SecretFieldComp;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DataStoreFormatter;
import io.xpipe.extension.util.DialogHelper;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.util.List;
public class SshStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
SshStore s = store.asNeeded();
return !ShellStore.isLocal(s.getProxy()) ? s.getProxy() : null;
}
@Override
public boolean isShareable() {
return true;
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
SshStore st = (SshStore) store.getValue();
var shellProp = new SimpleObjectProperty<>(st.getProxy());
var host = new SimpleObjectProperty<>(st.getHost() != null ? st.getHost() : null);
var port = new SimpleObjectProperty<>(st.getPort());
var user = new SimpleStringProperty(st.getUser());
var pass = new SimpleObjectProperty<>(st.getPassword());
var key = new SimpleObjectProperty<>(st.getKey());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(
I18n.observable("proxy"),
ShellStoreChoiceComp.host(st, shellProp),
shellProp)
.nonNull(val)
.addString(I18n.observable("host"), host)
.nonNull(val)
.addInteger(I18n.observable("port"), port)
.nonNull(val)
.addString(I18n.observable("user"), user)
.nonNull(val)
.addSecret(I18n.observable("password"), pass)
.addComp(keyFileConfig(key), key)
.bind(
() -> {
return new SshStore(
shellProp.get(), host.get(), port.get(), user.get(), pass.get(), key.get());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
private Comp<?> keyFileConfig(ObjectProperty<SshStore.SshKey> key) {
var keyFileProperty = new SimpleObjectProperty<FileStore>(
key.get() != null
? FileStore.builder()
.fileSystem(new LocalStore())
.file(key.get().getFile().toString())
.build()
: null);
var keyPasswordProperty = new SimpleObjectProperty<SecretValue>(
key.get() != null ? key.get().getPassword() : null);
return new DynamicOptionsBuilder(false)
.addTitle("key")
.addComp(
"keyFile", new FileStoreChoiceComp(List.of(new LocalStore()), keyFileProperty), keyFileProperty)
.addComp("keyPassword", new SecretFieldComp(keyPasswordProperty), keyPasswordProperty)
.bind(
() -> {
return keyFileProperty.get() != null
? SshStore.SshKey.builder()
.file(keyFileProperty
.get()
.getFile())
.password(keyPasswordProperty.get())
.build()
: null;
},
key)
.buildComp();
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
SshStore s = store.asNeeded();
return s.queryMachineName();
}
@Override
public String toSummaryString(DataStore store, int length) {
SshStore s = store.asNeeded();
var portSuffix = s.getPort() == 22 ? "" : ":" + s.getPort();
return DataStoreFormatter.formatViaProxy(
l -> {
var hostNameLength =
Math.max(l - portSuffix.length() - s.getUser().length() - 1, 0);
return s.getUser() + "@" + DataStoreFormatter.formatHostName(s.getHost(), hostNameLength)
+ portSuffix;
},
s.getProxy(),
length);
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(SshStore.class);
}
@Override
public DataStore defaultStore() {
return new SshStore(ShellStore.local(), null, 22, null, null, null);
}
@Override
public List<String> getPossibleNames() {
return List.of("ssh");
}
@Override
public Dialog dialogForStore(DataStore store) {
SshStore sshStore = store.asNeeded();
var address = new DialogHelper.Address(sshStore.getHost(), sshStore.getPort());
var addressQuery = DialogHelper.addressQuery(address);
var usernameQuery = DialogHelper.userQuery(sshStore.getUser());
var passwordQuery = DialogHelper.passwordQuery(sshStore.getPassword());
return Dialog.chain(addressQuery, usernameQuery, passwordQuery).evaluateTo(() -> {
DialogHelper.Address newAddress = addressQuery.getResult();
return new SshStore(
ShellStore.local(),
newAddress.getHostname(),
newAddress.getPort(),
usernameQuery.getResult(),
passwordQuery.getResult(),
null);
});
}
}

View file

@ -0,0 +1,111 @@
package io.xpipe.ext.proc.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.util.Validators;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@SuperBuilder
@Jacksonized
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@JsonTypeName("wsl")
public class WslStore extends JacksonizedValue implements MachineStore {
ShellStore host;
String distribution;
String user;
public WslStore(ShellStore host, String distribution, String user) {
this.host = host;
this.distribution = distribution;
this.user = user;
}
public static boolean isSupported(ShellStore host) {
return ShellStore.local()
.create()
.command("wsl --list")
.customCharset(StandardCharsets.UTF_16LE)
.startAndCheckExit();
}
public static Optional<String> getDefaultDistribution(ShellStore host) {
String s = null;
try (var pc = host.create()
.command("wsl --list")
.customCharset(StandardCharsets.UTF_16LE)
.start()) {
s = pc.readOnlyStdout();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return Optional.empty();
}
var def = s.lines()
.skip(1)
.filter(line -> {
return line.trim().endsWith("(Default)");
})
.findFirst();
return def.map(line -> line.replace(" (Default)", ""));
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(host, "Host");
host.checkComplete();
Validators.nonNull(distribution, "Distribution");
Validators.nonNull(user, "User");
}
@Override
public void validate() throws Exception {
Validators.hostFeature(host, WslStore::isSupported, "wsl");
}
@Override
public ShellProcessControl createControl() {
var l = createCommand();
return host.create()
.subShell(
shellProcessControl ->
shellProcessControl.getShellType().flatten(l),
(shellProcessControl, s) -> {
var flattened = shellProcessControl.getShellType().flatten(l);
if (s != null) {
flattened += " " + s;
}
return flattened;
});
}
private List<String> createCommand() {
var l = new ArrayList<String>(List.of("wsl"));
if (user != null) {
l.add("-u");
l.add(user);
}
if (distribution != null) {
l.add("--distribution");
l.add(distribution);
}
return l;
}
}

View file

@ -0,0 +1,118 @@
package io.xpipe.ext.proc.store;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DataStoreFormatter;
import io.xpipe.extension.util.DialogHelper;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class WslStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
WslStore s = store.asNeeded();
return s.getHost();
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
WslStore st = (WslStore) store.getValue();
Property<ShellStore> shellProp = new SimpleObjectProperty<>(st.getHost());
Property<String> distProp = new SimpleObjectProperty<>(st.getDistribution());
shellProp.addListener((observable, oldValue, newValue) -> {
distProp.setValue(WslStore.getDefaultDistribution(newValue).orElse(null));
});
Property<String> userProp = new SimpleObjectProperty<>(st.getUser());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(
I18n.observable("host"),
ShellStoreChoiceComp.host(st, shellProp),
shellProp)
.nonNull(val)
.addString(I18n.observable("proc.distribution"), distProp)
.nonNull(val)
.addString(I18n.observable("proc.username"), userProp)
.nonNull(val)
.bind(
() -> {
return new WslStore(shellProp.getValue(), distProp.getValue(), userProp.getValue());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public boolean isShareable() {
return true;
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
WslStore s = store.asNeeded();
return String.format("%s %s", "WSL", s.queryMachineName());
}
@Override
public String toSummaryString(DataStore store, int length) {
WslStore s = store.asNeeded();
return DataStoreFormatter.formatSubHost(
value -> DataStoreFormatter.cut(s.getUser() + "@" + s.getDistribution(), value), s.getHost(), length);
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(WslStore.class);
}
@Override
public DataStore defaultStore() {
return new WslStore(
new LocalStore(),
WslStore.getDefaultDistribution(new LocalStore()).orElse(null),
"root");
}
@Override
public List<String> getPossibleNames() {
return List.of("wsl");
}
@Override
public Dialog dialogForStore(DataStore store) {
WslStore wslStore = store.asNeeded();
var hostQ = DialogHelper.shellQuery("WSL Host", wslStore.getHost());
var distQ = Dialog.lazy(() -> Dialog.query(
"Distribution",
false,
true,
false,
wslStore.getDistribution() != null
? wslStore.getDistribution()
: WslStore.getDefaultDistribution(hostQ.getResult()).orElse(null),
QueryConverter.STRING));
var userQ = Dialog.query("Username", false, true, false, wslStore.getUser(), QueryConverter.STRING);
return Dialog.chain(hostQ, distQ, userQ).evaluateTo(() -> {
return new WslStore(hostQ.getResult(), distQ.getResult(), userQ.getResult());
});
}
@Override
public String getDisplayIconFileName() {
return "proc:wsl_icon.svg";
}
}

View file

@ -0,0 +1,50 @@
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.ext.proc.*;
import io.xpipe.ext.proc.action.InstallConnectorAction;
import io.xpipe.ext.proc.action.LaunchAction;
import io.xpipe.ext.proc.action.LaunchShortcutAction;
import io.xpipe.ext.proc.augment.*;
import io.xpipe.ext.proc.store.*;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ActionProvider;
open module io.xpipe.ext.proc {
uses io.xpipe.ext.proc.augment.CommandAugmentation;
exports io.xpipe.ext.proc;
exports io.xpipe.ext.proc.store;
exports io.xpipe.ext.proc.augment;
requires static lombok;
requires static javafx.base;
requires static javafx.controls;
requires io.xpipe.core;
requires com.fasterxml.jackson.databind;
requires static com.jcraft.jsch;
requires static io.xpipe.extension;
requires static io.xpipe.app;
requires static commons.exec;
requires static com.dlsc.preferencesfx;
provides PrefsProvider with
ProcPrefs;
provides CommandAugmentation with
SshCommandAugmentation,
CmdCommandAugmentation,
PosixShellCommandAugmentation,
PowershellCommandAugmentation;
provides ProcessControlProvider with
ProcProvider;
provides ActionProvider with
InstallConnectorAction,
LaunchShortcutAction,
LaunchAction;
provides DataStoreProvider with
SshStoreProvider,
ShellEnvironmentStoreProvider,
CommandStoreProvider,
DockerStoreProvider,
ShellCommandStoreProvider,
WslStoreProvider;
}

View file

@ -0,0 +1 @@
io.xpipe.ext.proc.ProcProvider

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 475 KiB

View file

@ -0,0 +1,3 @@
displayName=Mein Dateiformat
description=Meine Dateiformat-Beschreibung
fileName=Mein Dateiformat Datei

View file

@ -0,0 +1,96 @@
http.displayName=HTTP Request/Response
http.displayDescription=Handle body data of an HTTP request/response
cmd.displayName=Command
cmd.displayDescription=Handle the input and output of a command
file.displayName=File
file.displayDescription=Specify a file input
inMemory.displayName=In Memory
inMemory.displayDescription=Store binary data in memory
shellCommand.displayName=Shell Command
shellCommand.displayDescription=Open a shell with a custom command
shellEnvironment.displayName=Shell Environment
shellEnvironment.displayDescription=Run custom init commands on the shell connection
shellEnvironment.informationFormat=$TYPE$ environment
ssh.displayName=SSH Connection
ssh.displayDescription=Connect via SSH
binary.displayName=Binary
binary.displayDescription=Binary data
text.displayName=Text
text.displayDescription=Textural data start in plain text
sinkDrain.displayName=Sink Drain
sinkDrain.displayDescription=Construct a new sink drain
usage=Usage
anyBinaryFile=Any binary file
dataFile=Data file
binaryFile=Binary file
commandLine=Command Line
java=Java
streamOutput=Stream Output
localMachine=Local Machine
configuration=Configuration
selectOutput=Select Output
options=Options
requiresElevation=Run Elevated
commands=Commands
selectStore=Select Store
selectSource=Select Source
commandLineRead=Read
default=Default
commandLineWrite=Write
wslHost=WSL Host
timeout=Timeout
wsl.displayName=Windows Subsystem for Linux
wsl.displayDescription=Connect to a WSL instance running on Windows
docker.displayName=Docker Container
docker.displayDescription=Connect to a docker container
fileOutput=File Output
rawStreamOutput=Raw Stream Output
dataSourceOutput=Data Source Output
type=Type
input=Input
machine=Machine
bytes=$N$ bytes
container=Container
createShortcut=Create desktop shortcut
host=Host
port=Port
user=User
password=Password
method=Method
uri=URL
proxy=Proxy
distribution=Distribution
username=Username
terminal=Terminal
terminalProgram=Terminal program
program=Program
customTerminalCommand=Custom terminal command
cmd=cmd.exe
powershell=Powershell
windowsTerminal=Windows Terminal
gnomeTerminal=Gnome Terminal
shellType=Shell Type
command=Command
target=Target
writeMode=Write Mode
exportStream=Export Stream
browseFile=Browse File
openShell=Open Shell in Terminal
openCommand=Execute Command in Terminal
editFile=Edit File
description=Description
unconnected=Unconnected
waitingForConsumer=Waiting for Consumer
waitingForProducer=Waiting for Producer
open=Open
closed=Closed
keyFile=Key File
keyPassword=Key Password
key=Key
installConnector=Install Connector
konsole=Konsole
xfce=Xfce
macosTerminal=Terminal
iterm2=iTerm2
warp=Warp
custom=Custom

View file

@ -0,0 +1,12 @@
open module io.xpipe.ext.text.test {
requires io.xpipe.ext.proc;
requires org.junit.jupiter.api;
requires org.junit.jupiter.params;
requires io.xpipe.core;
requires io.xpipe.api;
requires io.xpipe.extension;
requires static lombok;
requires org.apache.commons.lang3;
exports test;
}

View file

@ -0,0 +1,64 @@
package test;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellTypes;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.SecretValue;
import io.xpipe.ext.proc.store.CommandStore;
import io.xpipe.ext.proc.store.WslStore;
import io.xpipe.extension.test.LocalExtensionTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.util.List;
public class CommandStoreTest extends LocalExtensionTest {
@Test
public void testCmdRead() throws Exception {
var store = CommandStore.builder()
.host(ShellStore.local())
.shell(ShellTypes.CMD)
.flow(DataFlow.INPUT)
.cmd("echo hi& echo there")
.build();
try (InputStream inputStream = store.openInput()) {
var read = new String(inputStream.readAllBytes());
Assertions.assertEquals("hi\r\nthere\r\n", read);
}
}
@Test
public void testpowershellReadAndWritea() throws Exception {
try (ShellProcessControl pc = new WslStore(ShellStore.local(), null, null)
.create()
.subShell(ShellTypes.BASH)
.start()) {
try (var command = pc.command(List.of("echo", "hi")).start()) {
var read = command.readOnlyStdout();
Assertions.assertEquals("hi", read);
}
try (var command = pc.command(List.of("echo", "there")).start()) {
var read = command.readOnlyStdout();
Assertions.assertEquals("there", read);
}
}
}
@Test
public void testWslElevation() throws Exception {
try (ShellProcessControl pc = new WslStore(ShellStore.local(), null, null)
.create()
.subShell(ShellTypes.BASH)
.elevation(SecretValue.encrypt("123"))
.start()) {
try (var command = pc.command(List.of("echo", "hi")).elevated().start()) {
var read = command.readOnlyStdout();
Assertions.assertEquals("hi", read);
}
}
}
}

View file

@ -0,0 +1,95 @@
package test;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.extension.test.LocalExtensionTest;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import test.item.CommandCheckTestItem;
import test.item.ShellTestItem;
import java.util.stream.Stream;
public class CommandTests extends LocalExtensionTest {
static Stream<Arguments> commandChecksProvider() {
Stream.Builder<Arguments> argumentBuilder = Stream.builder();
for (var arg : ShellTestItem.getAll().toList()) {
for (var c : CommandCheckTestItem.values()) {
argumentBuilder.add(Arguments.of(arg, Named.of(c.name(), c)));
}
}
return argumentBuilder.build();
}
@ParameterizedTest
@MethodSource("commandChecksProvider")
public void testCommandChecks(ShellProcessControl shellTestItem, CommandCheckTestItem tc) throws Exception {
try (var pc = shellTestItem.start()) {
try (var c = pc.command(tc.getCommandFunction().apply(pc)).start()) {
tc.getCommandCheck().accept(c);
}
}
}
@ParameterizedTest
@MethodSource("commandChecksProvider")
public void testDoubleCommandChecks(ShellProcessControl shellTestItem, CommandCheckTestItem tc) throws Exception {
try (var pc = shellTestItem.start()) {
try (var c = pc.command(tc.getCommandFunction().apply(pc)).start()) {
tc.getCommandCheck().accept(c);
}
try (var c = pc.command(tc.getCommandFunction().apply(pc)).start()) {
tc.getCommandCheck().accept(c);
}
}
}
@ParameterizedTest
@MethodSource("commandChecksProvider")
public void testSubCommandChecks(ShellProcessControl shellTestItem, CommandCheckTestItem tc) throws Exception {
try (var pc = shellTestItem.start()) {
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
try (var c = sub.command(tc.getCommandFunction().apply(sub)).start()) {
tc.getCommandCheck().accept(c);
}
}
}
}
@ParameterizedTest
@MethodSource("commandChecksProvider")
public void testSubDoubleCommandChecks(ShellProcessControl shellTestItem, CommandCheckTestItem tc) throws Exception {
try (var pc = shellTestItem.start()) {
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
try (var c = sub.command(tc.getCommandFunction().apply(sub)).start()) {
tc.getCommandCheck().accept(c);
}
try (var c = sub.command(tc.getCommandFunction().apply(sub)).start()) {
tc.getCommandCheck().accept(c);
}
}
}
}
@ParameterizedTest
@MethodSource("commandChecksProvider")
public void testDoubleSubCommandChecks(ShellProcessControl shellTestItem, CommandCheckTestItem tc) throws Exception {
try (var pc = shellTestItem.start()) {
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
try (var c = sub.command(tc.getCommandFunction().apply(sub)).start()) {
tc.getCommandCheck().accept(c);
}
}
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
try (var c = sub.command(tc.getCommandFunction().apply(sub)).start()) {
tc.getCommandCheck().accept(c);
}
}
}
}
}

View file

@ -0,0 +1,83 @@
package test;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.extension.test.LocalExtensionTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
public class FailureTests extends LocalExtensionTest {
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testFailedShellOpener(ShellProcessControl pc) throws Exception {
pc.start();
var sub = pc.subShell(
pc.getShellType().executeCommandWithShell(pc.getShellType().getEchoCommand("hi", false)));
Assertions.assertThrows(IOException.class, () -> {
sub.start();
});
Assertions.assertFalse(pc.isRunning());
Assertions.assertFalse(sub.isRunning());
}
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testInvalidShellOpenerCommand(ShellProcessControl pc) throws Exception {
pc.start();
var sub = pc.subShell("abc");
Assertions.assertThrows(IOException.class, () -> {
sub.start();
});
Assertions.assertFalse(pc.isRunning());
Assertions.assertFalse(sub.isRunning());
}
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testLoopingRecoveryShellOpener(ShellProcessControl pc) throws Exception {
pc.start();
var sub = pc.subShell("for i in {1..150}; do echo -n \"a\"; sleep 0.1s; done");
Assertions.assertThrows(IOException.class, () -> {
sub.start();
});
Assertions.assertFalse(pc.isRunning());
Assertions.assertFalse(sub.isRunning());
}
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testLoopingShellOpener(ShellProcessControl pc) throws Exception {
pc.start();
var sub = pc.subShell("for i in {1..150}; do echo hi; sleep 0.03s; done");
Assertions.assertThrows(IOException.class, () -> {
sub.start();
});
Assertions.assertFalse(pc.isRunning());
Assertions.assertFalse(sub.isRunning());
}
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testFrozenShellOpener(ShellProcessControl pc) throws Exception {
pc.start();
var sub = pc.subShell("sleep 30");
Assertions.assertThrows(IOException.class, () -> {
sub.start();
});
Assertions.assertFalse(pc.isRunning());
Assertions.assertFalse(sub.isRunning());
}
}

View file

@ -0,0 +1,56 @@
package test;
import io.xpipe.api.DataText;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellTypes;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.UUID;
public class FileTest extends DaemonExtensionTest {
static DataText referenceFile;
@BeforeAll
public static void setupStorage() throws Exception {
referenceFile = getSource("text", "utf8-bom-lf.txt").asText();
}
@ParameterizedTest
@MethodSource("test.item.ShellTestItem#getAll")
public void testReadAndWrite(ShellStore store) throws Exception {
try (var pc = store.create().start()) {
var file = getTestFile(pc);
var fileStore = FileStore.builder()
.fileSystem((FileSystemStore) store)
.file(file)
.build();
var source = getSource("text", fileStore).asText();
referenceFile.forwardTo(source);
var read = source.readAll();
Assertions.assertEquals(referenceFile.readAll(), read);
}
}
private String getTestFile(ShellProcessControl pc) throws Exception {
return getTemporaryDirectory(pc) + "/xpipe_test/" + UUID.randomUUID() + "/" + UUID.randomUUID() + ".txt";
}
private String getTemporaryDirectory(ShellProcessControl pc) throws Exception {
if (pc.getOsType().equals(OsType.WINDOWS)) {
return pc.executeStringSimpleCommand(ShellTypes.CMD, "echo %TEMP%");
}
return "/var/tmp";
}
}

View file

@ -0,0 +1,80 @@
package test;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.extension.test.LocalExtensionTest;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import test.item.ShellCheckTestItem;
import test.item.ShellTestItem;
import java.util.stream.Stream;
public class ShellTests extends LocalExtensionTest {
static Stream<Arguments> shellChecksProvider() {
Stream.Builder<Arguments> argumentBuilder = Stream.builder();
for (var arg : ShellTestItem.getAll().toList()) {
for (var c : ShellCheckTestItem.values()) {
argumentBuilder.add(Arguments.of(arg, Named.of(c.name(), c)));
}
}
return argumentBuilder.build();
}
@ParameterizedTest
@MethodSource("shellChecksProvider")
public void testShellChecks(ShellProcessControl shellTestItem, ShellCheckTestItem ti) throws Exception {
try (var pc = shellTestItem.start()) {
ti.getShellCheck().accept(pc);
}
}
@ParameterizedTest
@MethodSource("shellChecksProvider")
public void testDoubleShellChecks(ShellProcessControl shellTestItem, ShellCheckTestItem ti) throws Exception {
try (var pc = shellTestItem.start()) {
ti.getShellCheck().accept(pc);
pc.start();
ti.getShellCheck().accept(pc);
}
}
@ParameterizedTest
@MethodSource("shellChecksProvider")
public void testSubShellChecks(ShellProcessControl shellTestItem, ShellCheckTestItem ti) throws Exception {
try (var pc = shellTestItem.start()) {
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
ti.getShellCheck().accept(sub);
}
}
}
@ParameterizedTest
@MethodSource("shellChecksProvider")
public void testSubDoubleShellChecks(ShellProcessControl shellTestItem, ShellCheckTestItem ti) throws Exception {
try (var pc = shellTestItem.start()) {
try (ShellProcessControl sub = pc.subShell(pc.getShellType()).start()) {
ti.getShellCheck().accept(sub);
sub.start();
ti.getShellCheck().accept(sub);
}
}
}
@ParameterizedTest
@MethodSource("shellChecksProvider")
public void testDoubleSubShellChecks(ShellProcessControl shellTestItem, ShellCheckTestItem ti) throws Exception {
try (var pc = shellTestItem.start()) {
ShellType t = pc.getShellType();
try (ShellProcessControl sub = pc.subShell(t).start()) {
ti.getShellCheck().accept(sub);
}
try (ShellProcessControl sub = pc.subShell(t).start()) {
ti.getShellCheck().accept(sub);
}
}
}
}

View file

@ -0,0 +1,34 @@
package test.item;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellTypes;
import io.xpipe.ext.proc.store.ShellCommandStore;
import io.xpipe.extension.test.TestModule;
import java.util.Map;
import java.util.function.Supplier;
public class BasicShellTestItem extends TestModule<ShellProcessControl> {
@Override
protected void init(Map<String, Supplier<ShellProcessControl>> list) {
list.put("local", () -> new LocalStore().create());
if (OsType.getLocal().equals(OsType.WINDOWS)) {
list.put("local pwsh", () -> ShellCommandStore.shell(new LocalStore(), ShellTypes.POWERSHELL)
.create());
list.put("local pwsh cmd", () -> ShellCommandStore.shell(
ShellCommandStore.shell(
ShellCommandStore.shell(new LocalStore(), ShellTypes.POWERSHELL), ShellTypes.CMD),
ShellTypes.CMD)
.create());
}
}
@Override
protected Class<ShellProcessControl> getValueClass() {
return ShellProcessControl.class;
}
}

View file

@ -0,0 +1,30 @@
package test.item;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellProcessControl;
import lombok.Getter;
import org.apache.commons.lang3.function.FailableConsumer;
import org.junit.jupiter.api.Assertions;
import java.util.function.Function;
@Getter
public enum CommandCheckTestItem {
ECHO(
shellProcessControl -> {
return shellProcessControl.getShellType().getEchoCommand("hi", false);
},
commandProcessControl -> {
Assertions.assertEquals("hi", commandProcessControl.readOrThrow());
});
private final Function<ShellProcessControl, String> commandFunction;
private final FailableConsumer<CommandProcessControl, Exception> commandCheck;
CommandCheckTestItem(
Function<ShellProcessControl, String> commandFunction,
FailableConsumer<CommandProcessControl, Exception> commandCheck) {
this.commandFunction = commandFunction;
this.commandCheck = commandCheck;
}
}

View file

@ -0,0 +1,112 @@
package test.item;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.ext.proc.util.ShellHelper;
import lombok.Getter;
import org.apache.commons.lang3.function.FailableConsumer;
import org.junit.jupiter.api.Assertions;
import java.util.List;
import java.util.UUID;
@Getter
public enum ShellCheckTestItem {
OS_NAME(shellProcessControl -> {
var os = ShellHelper.determineOsType(shellProcessControl);
os.determineOperatingSystemName(shellProcessControl);
}),
RESTART(shellProcessControl -> {
var s1 = shellProcessControl.executeStringSimpleCommand("echo hi");
Assertions.assertEquals("hi", s1);
shellProcessControl.restart();
var s2 = shellProcessControl.executeStringSimpleCommand("echo world");
Assertions.assertEquals("world", s2);
}),
INIT_FILE(shellProcessControl -> {
var content = "<contentß>";
try (var c = shellProcessControl
.subShell(shellProcessControl.getShellType())
.initWith(List.of(shellProcessControl.getShellType().getSetEnvironmentVariableCommand("testVar", content)))
.start()) {
var output = c.executeStringSimpleCommand(
shellProcessControl.getShellType().getPrintEnvironmentVariableCommand("testVar"));
Assertions.assertEquals(content, output);
}
}),
STREAM_WRITE(shellProcessControl -> {
var content = "hello\nworldß";
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
try (var c = shellProcessControl
.command(shellProcessControl.getShellType().getStreamFileWriteCommand(fileOne))
.start()) {
c.discardOut();
c.discardErr();
c.getStdin().write(content.getBytes(shellProcessControl.getCharset()));
c.closeStdin();
}
shellProcessControl.restart();
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
try (var c = shellProcessControl
.subShell(shellProcessControl.getShellType())
.command(shellProcessControl.getShellType().getStreamFileWriteCommand(fileTwo))
.start()) {
c.discardOut();
c.discardErr();
c.getStdin().write(content.getBytes(shellProcessControl.getCharset()));
c.closeStdin();
}
shellProcessControl.restart();
var s1 = shellProcessControl.executeStringSimpleCommand(
shellProcessControl.getShellType().getFileReadCommand(fileOne));
var s2 = shellProcessControl.executeStringSimpleCommand(
shellProcessControl.getShellType().getFileReadCommand(fileTwo));
Assertions.assertEquals(content, s1);
Assertions.assertEquals(content, s2);
}),
SIMPLE_WRITE(shellProcessControl -> {
var content = "hello worldß";
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
shellProcessControl.executeSimpleCommand(
shellProcessControl.getShellType().getTextFileWriteCommand(content, fileOne));
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
shellProcessControl.executeSimpleCommand(
shellProcessControl.getShellType().getTextFileWriteCommand(content, fileTwo));
var s1 = shellProcessControl.executeStringSimpleCommand(
shellProcessControl.getShellType().getFileReadCommand(fileOne));
var s2 = shellProcessControl.executeStringSimpleCommand(
shellProcessControl.getShellType().getFileReadCommand(fileTwo));
Assertions.assertEquals(content, s1);
Assertions.assertEquals(content, s2);
}),
TERMINAL_OPEN(shellProcessControl -> {
shellProcessControl.prepareIntermediateTerminalOpen(null);
}),
COMMAND_TERMINAL_OPEN(shellProcessControl -> {
for (CommandCheckTestItem v : CommandCheckTestItem.values()) {
shellProcessControl.prepareIntermediateTerminalOpen(v.getCommandFunction().apply(shellProcessControl));
}
}),
ECHO(shellProcessControl -> {
ShellHelper.determineOsType(shellProcessControl);
});
private final FailableConsumer<ShellProcessControl, Exception> shellCheck;
ShellCheckTestItem(FailableConsumer<ShellProcessControl, Exception> shellCheck) {
this.shellCheck = shellCheck;
}
}

View file

@ -0,0 +1,14 @@
package test.item;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.extension.test.TestModule;
import org.junit.jupiter.api.Named;
import java.util.stream.Stream;
public class ShellTestItem {
public static Stream<Named<ShellProcessControl>> getAll() {
return TestModule.getArguments(ShellProcessControl.class, "test.item.BasicShellTestItem", "test.item.PrivateShellTestItem");
}
}

View file

@ -0,0 +1,2 @@
hello
world

1
ext/procx/build.gradle Normal file
View file

@ -0,0 +1 @@
plugins { id 'java' }

View file

@ -0,0 +1 @@
module io.xpipe.ext.procx {}

View file

@ -79,7 +79,7 @@ public interface DataStoreProvider {
}
default String getModuleName() {
var n = getClass().getPackageName();
var n = getClass().getModule().getName();
var i = n.lastIndexOf('.');
return i != -1 ? n.substring(i + 1) : n;
}

View file

@ -1,7 +1,7 @@
package io.xpipe.extension;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ProxyFunction;
import io.xpipe.extension.event.TrackEvent;

View file

@ -42,7 +42,7 @@ public interface XPipeDistributionType {
@Override
public boolean supportsURLs() {
return OsType.getLocal().equals(OsType.MAC);
return OsType.getLocal().equals(OsType.MACOS);
}
@Override