Restructure proc module
16
README.md
|
@ -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
|
- [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
|
- [cli](cli) - The X-Pipe CLI implementation, a GraalVM native image application
|
||||||
- [dist](dist) - Tools to create a distributable package of X-Pipe
|
- [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
|
## Development
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ List<String> jvmRunArgs = [
|
||||||
"--add-exports", "org.apache.commons.lang3/org.apache.commons.lang3.math=io.xpipe.extension",
|
"--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.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", "com.dustinredmond.fxtrayicon/com.dustinredmond.fxtrayicon=io.xpipe.app",
|
||||||
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.extension",
|
"--add-opens", "net.synedra.validatorfx/net.synedra.validatorfx=io.xpipe.extension",
|
||||||
"-Xmx8g",
|
"-Xmx8g",
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.core;
|
||||||
|
|
||||||
import io.xpipe.app.Main;
|
import io.xpipe.app.Main;
|
||||||
import io.xpipe.app.comp.AppLayoutComp;
|
import io.xpipe.app.comp.AppLayoutComp;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
import io.xpipe.extension.fxcomps.util.PlatformThread;
|
import io.xpipe.extension.fxcomps.util.PlatformThread;
|
||||||
|
@ -10,7 +11,6 @@ import javafx.application.Platform;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
@ -38,7 +38,7 @@ public class App extends Application {
|
||||||
|
|
||||||
// Set dock icon explicitly on mac
|
// 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
|
// 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 {
|
try {
|
||||||
var iconUrl = Main.class.getResourceAsStream("resources/img/logo.png");
|
var iconUrl = Main.class.getResourceAsStream("resources/img/logo.png");
|
||||||
if (iconUrl != null) {
|
if (iconUrl != null) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class LauncherCommand implements Callable<Integer> {
|
||||||
OpenExchange.Request.builder().arguments(inputs).build());
|
OpenExchange.Request.builder().arguments(inputs).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
Desktop.getDesktop().setOpenURIHandler(e -> {
|
Desktop.getDesktop().setOpenURIHandler(e -> {
|
||||||
con.performSimpleExchange(
|
con.performSimpleExchange(
|
||||||
OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
|
OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build());
|
||||||
|
@ -120,7 +120,7 @@ public class LauncherCommand implements Callable<Integer> {
|
||||||
LauncherInput.handle(inputs);
|
LauncherInput.handle(inputs);
|
||||||
|
|
||||||
// URL open operations have to be handled in a special way on macOS!
|
// 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 -> {
|
Desktop.getDesktop().setOpenURIHandler(e -> {
|
||||||
LauncherInput.handle(List.of(e.getURI().toString()));
|
LauncherInput.handle(List.of(e.getURI().toString()));
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,7 +57,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSelectable() {
|
public boolean isSelectable() {
|
||||||
return OsType.getLocal().equals(OsType.MAC);
|
return OsType.getLocal().equals(OsType.MACOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,7 +5,6 @@ import io.xpipe.core.process.ShellTypes;
|
||||||
import io.xpipe.extension.prefs.PrefsChoiceValue;
|
import io.xpipe.extension.prefs.PrefsChoiceValue;
|
||||||
import io.xpipe.extension.util.ApplicationHelper;
|
import io.xpipe.extension.util.ApplicationHelper;
|
||||||
import io.xpipe.extension.util.WindowsRegistry;
|
import io.xpipe.extension.util.WindowsRegistry;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -38,13 +37,9 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<Path> determinePath() {
|
protected Optional<Path> determinePath() {
|
||||||
Optional<String> launcherDir = Optional.empty();
|
Optional<String> launcherDir;
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
launcherDir = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null)
|
||||||
launcherDir = WindowsRegistry.readString(
|
|
||||||
WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null)
|
|
||||||
.map(p -> p + "\\notepad++.exe");
|
.map(p -> p + "\\notepad++.exe");
|
||||||
}
|
|
||||||
|
|
||||||
return launcherDir.map(Path::of);
|
return launcherDir.map(Path::of);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -71,7 +66,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(Path file) throws Exception {
|
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +104,6 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
|
|
||||||
public void launch(Path file) throws Exception;
|
public void launch(Path file) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public static class LinuxPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
|
public static class LinuxPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
|
||||||
|
|
||||||
public LinuxPathType(String id, String command) {
|
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) {
|
public WindowsFullPathType(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
@ -147,8 +143,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
public static final List<ExternalEditorType> WINDOWS_EDITORS = List.of(VSCODE, NOTEPADPLUSPLUS_WINDOWS, NOTEPAD);
|
public static final List<ExternalEditorType> WINDOWS_EDITORS = List.of(VSCODE, NOTEPADPLUSPLUS_WINDOWS, NOTEPAD);
|
||||||
public static final List<LinuxPathType> LINUX_EDITORS =
|
public static final List<LinuxPathType> LINUX_EDITORS =
|
||||||
List.of(VSCODE_LINUX, NOTEPADPLUSPLUS_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD);
|
List.of(VSCODE_LINUX, NOTEPADPLUSPLUS_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD);
|
||||||
public static final List<ExternalEditorType> MACOS_EDITORS =
|
public static final List<ExternalEditorType> MACOS_EDITORS = List.of(VSCODE_MACOS, SUBLIME_MACOS, TEXT_EDIT);
|
||||||
List.of(VSCODE_MACOS, SUBLIME_MACOS, TEXT_EDIT);
|
|
||||||
|
|
||||||
public static final List<ExternalEditorType> ALL = ((Supplier<List<ExternalEditorType>>) () -> {
|
public static final List<ExternalEditorType> ALL = ((Supplier<List<ExternalEditorType>>) () -> {
|
||||||
var all = new ArrayList<ExternalEditorType>();
|
var all = new ArrayList<ExternalEditorType>();
|
||||||
|
@ -158,12 +153,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
all.addAll(LINUX_EDITORS);
|
all.addAll(LINUX_EDITORS);
|
||||||
}
|
}
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
all.addAll(MACOS_EDITORS);
|
all.addAll(MACOS_EDITORS);
|
||||||
}
|
}
|
||||||
all.add(CUSTOM);
|
all.add(CUSTOM);
|
||||||
return all;
|
return all;
|
||||||
}).get();
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
public static void detectDefault() {
|
public static void detectDefault() {
|
||||||
var typeProperty = AppPrefs.get().externalEditor;
|
var typeProperty = AppPrefs.get().externalEditor;
|
||||||
|
@ -196,7 +192,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
typeProperty.set(MACOS_EDITORS.stream()
|
typeProperty.set(MACOS_EDITORS.stream()
|
||||||
.filter(externalEditorType -> externalEditorType.isAvailable())
|
.filter(externalEditorType -> externalEditorType.isAvailable())
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
|
@ -121,6 +121,7 @@ public class DataStoreEntry extends StorageElement {
|
||||||
var information = Optional.ofNullable(json.get("information"))
|
var information = Optional.ofNullable(json.get("information"))
|
||||||
.map(JsonNode::textValue)
|
.map(JsonNode::textValue)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
var lastUsed = Instant.parse(json.required("lastUsed").textValue());
|
var lastUsed = Instant.parse(json.required("lastUsed").textValue());
|
||||||
var lastModified = Instant.parse(json.required("lastModified").textValue());
|
var lastModified = Instant.parse(json.required("lastModified").textValue());
|
||||||
var configuration = Optional.ofNullable(json.get("configuration"))
|
var configuration = Optional.ofNullable(json.get("configuration"))
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
package io.xpipe.app.storage;
|
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.ErrorEvent;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -22,44 +20,7 @@ public class StandardStorage extends DataStorage {
|
||||||
private DataSourceCollection recovery;
|
private DataSourceCollection recovery;
|
||||||
|
|
||||||
private boolean isNewSession() {
|
private boolean isNewSession() {
|
||||||
try {
|
return XPipeSession.get().isNewSystemSession();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteLeftovers() {
|
private void deleteLeftovers() {
|
||||||
|
@ -322,8 +283,6 @@ public class StandardStorage extends DataStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLeftovers();
|
deleteLeftovers();
|
||||||
|
|
||||||
writeSessionInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class AppInstaller {
|
||||||
return Files.exists(Path.of("/etc/debian_version")) ? new InstallerAssetType.Debian() : new InstallerAssetType.Rpm();
|
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();
|
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();
|
return new InstallerAssetType.Pkg();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.app.core.AppExtensionManager;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
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.core.util.XPipeSession;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
|
|
@ -112,9 +112,7 @@ public abstract class Charsetter {
|
||||||
|
|
||||||
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
|
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
|
||||||
if (result.getNewLine() == null) {
|
if (result.getNewLine() == null) {
|
||||||
try (var pc = m.create().start()) {
|
result = new Result(result.getCharset(), m.getShellType() != null ? m.getShellType().getNewLine() : null);
|
||||||
result = new Result(result.getCharset(), pc.getShellType().getNewLine());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.core.impl;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
import io.xpipe.core.store.FileSystemStore;
|
import io.xpipe.core.store.FileSystemStore;
|
||||||
import io.xpipe.core.store.MachineStore;
|
import io.xpipe.core.store.MachineStore;
|
||||||
|
@ -47,7 +48,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShellProcessControl create() {
|
public ShellProcessControl createControl() {
|
||||||
return ProcessControlProvider.createLocal();
|
return ProcessControlProvider.createLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -7,12 +7,12 @@ public interface OsType {
|
||||||
|
|
||||||
Windows WINDOWS = new Windows();
|
Windows WINDOWS = new Windows();
|
||||||
Linux LINUX = new Linux();
|
Linux LINUX = new Linux();
|
||||||
Mac MAC = new Mac();
|
MacOs MACOS = new MacOs();
|
||||||
|
|
||||||
public static OsType getLocal() {
|
public static OsType getLocal() {
|
||||||
String osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
|
String osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
|
||||||
if ((osName.contains("mac")) || (osName.contains("darwin"))) {
|
if ((osName.contains("mac")) || (osName.contains("darwin"))) {
|
||||||
return MAC;
|
return MACOS;
|
||||||
} else if (osName.contains("win")) {
|
} else if (osName.contains("win")) {
|
||||||
return WINDOWS;
|
return WINDOWS;
|
||||||
} else if (osName.contains("nux")) {
|
} else if (osName.contains("nux")) {
|
||||||
|
@ -124,7 +124,7 @@ public interface OsType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Mac implements OsType {
|
static class MacOs implements OsType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTempDirectory(ShellProcessControl pc) throws Exception {
|
public String getTempDirectory(ShellProcessControl pc) throws Exception {
|
||||||
|
|
|
@ -1,6 +1,58 @@
|
||||||
package io.xpipe.core.process;
|
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 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,14 @@ import lombok.NonNull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public interface ShellProcessControl extends ProcessControl {
|
public interface ShellProcessControl extends ProcessControl {
|
||||||
|
|
||||||
|
void onInit(Consumer<ShellProcessControl> pc);
|
||||||
|
|
||||||
String prepareTerminalOpen() throws Exception;
|
String prepareTerminalOpen() throws Exception;
|
||||||
|
|
||||||
String prepareIntermediateTerminalOpen(String content) throws Exception;
|
String prepareIntermediateTerminalOpen(String content) throws Exception;
|
||||||
|
|
|
@ -199,18 +199,15 @@ public class ShellTypes {
|
||||||
@Override
|
@Override
|
||||||
public Charset determineCharset(ShellProcessControl control) throws Exception {
|
public Charset determineCharset(ShellProcessControl control) throws Exception {
|
||||||
control.writeLine("chcp");
|
control.writeLine("chcp");
|
||||||
|
var pattern = Pattern.compile("^[\\w ]+: (\\d+)$");
|
||||||
var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII));
|
var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII));
|
||||||
// Read echo of command
|
while (true) {
|
||||||
r.readLine();
|
|
||||||
// Read actual output
|
|
||||||
var line = r.readLine();
|
var line = r.readLine();
|
||||||
// Read additional empty line
|
var matcher = pattern.matcher(line);
|
||||||
r.readLine();
|
if (matcher.matches()) {
|
||||||
|
return Charset.forName("ibm" + matcher.group(1));
|
||||||
var matcher = Pattern.compile("\\d+").matcher(line);
|
}
|
||||||
matcher.find();
|
}
|
||||||
return Charset.forName("ibm" + matcher.group());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,10 +2,14 @@ package io.xpipe.core.store;
|
||||||
|
|
||||||
import io.xpipe.core.charsetter.Charsetter;
|
import io.xpipe.core.charsetter.Charsetter;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
import io.xpipe.core.impl.LocalStore;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
import io.xpipe.core.process.ShellType;
|
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() {
|
public static MachineStore local() {
|
||||||
return new LocalStore();
|
return new LocalStore();
|
||||||
|
@ -21,7 +25,28 @@ public interface ShellStore extends DataStore {
|
||||||
return s instanceof LocalStore;
|
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 {
|
public default ShellType determineType() throws Exception {
|
||||||
try (var pc = create().start()) {
|
try (var pc = create().start()) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class XPipeInstallation {
|
||||||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
return "nohup \"" + installationBase + "/app/bin/xpiped\" --mode " + mode.getDisplayName() + suffix
|
return "nohup \"" + installationBase + "/app/bin/xpiped\" --mode " + mode.getDisplayName() + suffix
|
||||||
+ " & disown";
|
+ " & disown";
|
||||||
} else if (OsType.getLocal().equals(OsType.MAC)) {
|
} else if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
return "open \"" + installationBase + "\" --args --mode " + mode.getDisplayName() + suffix;
|
return "open \"" + installationBase + "\" --args --mode " + mode.getDisplayName() + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class XPipeInstallation {
|
||||||
|
|
||||||
public static boolean isInstallationDistribution() {
|
public static boolean isInstallationDistribution() {
|
||||||
var base = getLocalInstallationBasePath();
|
var base = getLocalInstallationBasePath();
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
if (!base.toString().equals(getLocalDefaultInstallationBasePath(false))) {
|
if (!base.toString().equals(getLocalDefaultInstallationBasePath(false))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -93,13 +93,13 @@ public class XPipeInstallation {
|
||||||
|
|
||||||
public static Path getLocalExtensionsDirectory() {
|
public static Path getLocalExtensionsDirectory() {
|
||||||
Path path = getLocalInstallationBasePath();
|
Path path = getLocalInstallationBasePath();
|
||||||
return OsType.getLocal().equals(OsType.MAC)
|
return OsType.getLocal().equals(OsType.MACOS)
|
||||||
? path.resolve("Contents").resolve("Resources").resolve("extensions")
|
? path.resolve("Contents").resolve("Resources").resolve("extensions")
|
||||||
: path.resolve("app").resolve("extensions");
|
: path.resolve("app").resolve("extensions");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getLocalInstallationBasePathForJavaExecutable(Path executable) {
|
private static Path getLocalInstallationBasePathForJavaExecutable(Path executable) {
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
return executable
|
return executable
|
||||||
.getParent()
|
.getParent()
|
||||||
.getParent()
|
.getParent()
|
||||||
|
@ -115,7 +115,7 @@ public class XPipeInstallation {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getLocalInstallationBasePathForDaemonExecutable(Path executable) {
|
private static Path getLocalInstallationBasePathForDaemonExecutable(Path executable) {
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
return executable.getParent().getParent().getParent();
|
return executable.getParent().getParent().getParent();
|
||||||
} else if (OsType.getLocal().equals(OsType.LINUX)) {
|
} else if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
return executable.getParent().getParent().getParent();
|
return executable.getParent().getParent().getParent();
|
||||||
|
@ -138,7 +138,7 @@ public class XPipeInstallation {
|
||||||
return defaultInstallation;
|
return defaultInstallation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||||
return FileNames.getParent(FileNames.getParent(FileNames.getParent(cliExecutable)));
|
return FileNames.getParent(FileNames.getParent(FileNames.getParent(cliExecutable)));
|
||||||
} else {
|
} else {
|
||||||
return FileNames.getParent(FileNames.getParent(cliExecutable));
|
return FileNames.getParent(FileNames.getParent(cliExecutable));
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class XPipeTempDirectory {
|
||||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileExistsCommand(dir))) {
|
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileExistsCommand(dir))) {
|
||||||
proc.executeSimpleCommand(proc.getShellType().flatten(proc.getShellType().getMkdirsCommand(dir)), "Unable to access or create temporary directory " + 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 + "\"");
|
proc.executeSimpleCommand("chmod -f 777 \"" + dir + "\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.source.WriteMode;
|
||||||
import io.xpipe.core.util.CoreJacksonModule;
|
import io.xpipe.core.util.CoreJacksonModule;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.ext.pdx.parser;
|
||||||
import io.xpipe.core.data.node.DataStructureNode;
|
import io.xpipe.core.data.node.DataStructureNode;
|
||||||
import io.xpipe.core.data.node.TupleNode;
|
import io.xpipe.core.data.node.TupleNode;
|
||||||
import io.xpipe.core.data.node.ValueNode;
|
import io.xpipe.core.data.node.ValueNode;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -40,7 +39,7 @@ public final class TextFormatParser {
|
||||||
|
|
||||||
public static TextFormatParser eu4() {
|
public static TextFormatParser eu4() {
|
||||||
return new TextFormatParser(
|
return new TextFormatParser(
|
||||||
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
|
Charset.forName("windows-1252"),
|
||||||
TaggedNodes.NO_TAGS,
|
TaggedNodes.NO_TAGS,
|
||||||
s -> s.equals("map_area_data"));
|
s -> s.equals("map_area_data"));
|
||||||
}
|
}
|
||||||
|
@ -59,14 +58,14 @@ public final class TextFormatParser {
|
||||||
|
|
||||||
public static TextFormatParser ck2() {
|
public static TextFormatParser ck2() {
|
||||||
return new TextFormatParser(
|
return new TextFormatParser(
|
||||||
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
|
Charset.forName("windows-1252"),
|
||||||
TaggedNodes.NO_TAGS,
|
TaggedNodes.NO_TAGS,
|
||||||
s -> false);
|
s -> false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextFormatParser vic2() {
|
public static TextFormatParser vic2() {
|
||||||
return new TextFormatParser(
|
return new TextFormatParser(
|
||||||
SystemUtils.IS_OS_MAC ? StandardCharsets.UTF_8 : Charset.forName("windows-1252"),
|
Charset.forName("windows-1252"),
|
||||||
TaggedNodes.NO_TAGS,
|
TaggedNodes.NO_TAGS,
|
||||||
s -> false);
|
s -> false);
|
||||||
}
|
}
|
||||||
|
|
39
ext/proc/build.gradle
Normal 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
|
||||||
|
}
|
241
ext/proc/src/main/java/io/xpipe/ext/proc/CommandControlImpl.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
ext/proc/src/main/java/io/xpipe/ext/proc/ProcPrefs.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
ext/proc/src/main/java/io/xpipe/ext/proc/ProcProvider.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
118
ext/proc/src/main/java/io/xpipe/ext/proc/ShellControlImpl.java
Normal 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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
64
ext/proc/src/main/java/io/xpipe/ext/proc/store/SshStore.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
111
ext/proc/src/main/java/io/xpipe/ext/proc/store/WslStore.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
50
ext/proc/src/main/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
io.xpipe.ext.proc.ProcProvider
|
|
@ -0,0 +1 @@
|
||||||
|
name=proc
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 475 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
displayName=Mein Dateiformat
|
||||||
|
description=Meine Dateiformat-Beschreibung
|
||||||
|
fileName=Mein Dateiformat Datei
|
|
@ -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
|
12
ext/proc/src/test/java/module-info.java
Normal 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;
|
||||||
|
}
|
64
ext/proc/src/test/java/test/CommandStoreTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
ext/proc/src/test/java/test/CommandTests.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
ext/proc/src/test/java/test/FailureTests.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
56
ext/proc/src/test/java/test/FileTest.java
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
80
ext/proc/src/test/java/test/ShellTests.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
ext/proc/src/test/java/test/item/BasicShellTestItem.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
30
ext/proc/src/test/java/test/item/CommandCheckTestItem.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
112
ext/proc/src/test/java/test/item/ShellCheckTestItem.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
14
ext/proc/src/test/java/test/item/ShellTestItem.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
2
ext/proc/src/test/resources/utf8-bom-lf.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
hello
|
||||||
|
world
|
1
ext/procx/build.gradle
Normal file
|
@ -0,0 +1 @@
|
||||||
|
plugins { id 'java' }
|
1
ext/procx/src/main/java/module-info.java
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module io.xpipe.ext.procx {}
|
|
@ -79,7 +79,7 @@ public interface DataStoreProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
default String getModuleName() {
|
default String getModuleName() {
|
||||||
var n = getClass().getPackageName();
|
var n = getClass().getModule().getName();
|
||||||
var i = n.lastIndexOf('.');
|
var i = n.lastIndexOf('.');
|
||||||
return i != -1 ? n.substring(i + 1) : n;
|
return i != -1 ? n.substring(i + 1) : n;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
import io.xpipe.core.impl.ProcessControlProvider;
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.core.util.ProxyFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
|
|
@ -42,7 +42,7 @@ public interface XPipeDistributionType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsURLs() {
|
public boolean supportsURLs() {
|
||||||
return OsType.getLocal().equals(OsType.MAC);
|
return OsType.getLocal().equals(OsType.MACOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|