From 37db891366be6578c68ee826ddaaf5be258c9213 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Wed, 7 Dec 2022 21:55:58 +0100 Subject: [PATCH] Fixes for shells --- .../java/io/xpipe/core/process/OsType.java | 10 ++-- .../core/process/ShellProcessControl.java | 12 ++-- .../java/io/xpipe/core/process/ShellType.java | 17 ++++-- .../io/xpipe/core/process/ShellTypes.java | 60 +++++++++---------- .../io/xpipe/core/store/MachineStore.java | 14 ----- .../java/io/xpipe/core/store/ShellStore.java | 11 ++++ .../java/io/xpipe/extension/XPipeProxy.java | 4 ++ .../fxcomps/impl/ProxyChoiceComp.java | 10 ++-- .../fxcomps/impl/ShellStoreChoiceComp.java | 3 +- .../extension/util/DataStoreFormatter.java | 38 +++++++++++- .../resources/lang/translations_en.properties | 1 + 11 files changed, 107 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/io/xpipe/core/process/OsType.java b/core/src/main/java/io/xpipe/core/process/OsType.java index 71efc63c..f833eb6c 100644 --- a/core/src/main/java/io/xpipe/core/process/OsType.java +++ b/core/src/main/java/io/xpipe/core/process/OsType.java @@ -47,7 +47,7 @@ public interface OsType { @Override public String getTempDirectory(ShellProcessControl pc) throws Exception { - return pc.executeSimpleCommand(ShellTypes.CMD, pc.getShellType().getPrintVariableCommand("TEMP")); + return pc.executeSimpleCommand(ShellTypes.CMD, ShellTypes.CMD.getPrintVariableCommand("TEMP")); } @Override @@ -111,7 +111,7 @@ public interface OsType { @Override public String determineOperatingSystemName(ShellProcessControl pc) throws Exception { try (CommandProcessControl c = - pc.command(ShellTypes.SH, "lsb_release -a").start()) { + pc.command("lsb_release -a").start()) { var text = c.readOnlyStdout(); if (c.getExitCode() == 0) { return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", null); @@ -119,7 +119,7 @@ public interface OsType { } try (CommandProcessControl c = - pc.command(ShellTypes.SH, "cat /etc/*release").start()) { + pc.command("cat /etc/*release").start()) { var text = c.readOnlyStdout(); if (c.getExitCode() == 0) { return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", null); @@ -127,7 +127,7 @@ public interface OsType { } String type = "Unknown"; - try (CommandProcessControl c = pc.command(ShellTypes.SH, "uname -o").start()) { + try (CommandProcessControl c = pc.command("uname -o").start()) { var text = c.readOnlyStdout(); if (c.getExitCode() == 0) { type = text.strip(); @@ -135,7 +135,7 @@ public interface OsType { } String version = "?"; - try (CommandProcessControl c = pc.command(ShellTypes.SH, "uname -r").start()) { + try (CommandProcessControl c = pc.command("uname -r").start()) { var text = c.readOnlyStdout(); if (c.getExitCode() == 0) { version = text.strip(); diff --git a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java index 5640653f..47e103a0 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; public interface ShellProcessControl extends ProcessControl { @@ -30,7 +29,9 @@ public interface ShellProcessControl extends ProcessControl { } default String executeSimpleCommand(ShellType type, String command) throws Exception { - return executeSimpleCommand(type.switchTo(command)); + try (var sub = subShell(type).start()) { + return sub.executeSimpleCommand(command); + } } void restart() throws Exception; @@ -53,13 +54,8 @@ public interface ShellProcessControl extends ProcessControl { return subShell(type.openCommand()).elevation(getElevationPassword()); } - default CommandProcessControl command(@NonNull ShellType type, String command) { - return command(type.switchTo(command)); - } - default ShellProcessControl subShell(@NonNull List command) { - return subShell( - command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "))); + return subShell(shellProcessControl -> shellProcessControl.getShellType().flatten(command)); } default ShellProcessControl subShell(@NonNull String command) { diff --git a/core/src/main/java/io/xpipe/core/process/ShellType.java b/core/src/main/java/io/xpipe/core/process/ShellType.java index c5cabb0b..c05a29b2 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellType.java +++ b/core/src/main/java/io/xpipe/core/process/ShellType.java @@ -23,16 +23,15 @@ public interface ShellType { String getOpenWithInitFileCommand(String file); default String flatten(List command) { - return command.stream().map(s -> s.contains(" ") && !(s.startsWith("\"") && s.endsWith("\"")) ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")); + return command.stream() + .map(s -> s.contains(" ") && !(s.startsWith("\"") && s.endsWith("\"")) ? "\"" + s + "\"" : s) + .collect(Collectors.joining(" ")); } - default String joinCommands(String... s) { return String.join(getConcatenationOperator(), s); } - String escape(String input); - void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception; default String getExitCommand() { @@ -57,13 +56,21 @@ public interface ShellType { String elevateConsoleCommand(ShellProcessControl control, String command); + default String getScriptEchoCommand(String s) { + return getEchoCommand(s, false); + } + String getEchoCommand(String s, boolean toErrorStream); String queryShellProcessId(ShellProcessControl control) throws Exception; String getSetVariableCommand(String variableName, String value); - String getPrintVariableCommand(String name); + default String getPrintVariableCommand(String name) { + return getPrintVariableCommand("", name); + } + + String getPrintVariableCommand(String prefix, String name); List openCommand(); diff --git a/core/src/main/java/io/xpipe/core/process/ShellTypes.java b/core/src/main/java/io/xpipe/core/process/ShellTypes.java index 32c10e77..72da64c8 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/process/ShellTypes.java @@ -50,17 +50,22 @@ public class ShellTypes { @Override public String getSetVariableCommand(String variableName, String value) { - return ("set \"" + variableName + "=" + value + "\"").replaceAll("!", "^!"); + return ("set \"" + variableName + "=" + value.replaceAll("\"", "^$0") + "\""); } @Override - public String getPrintVariableCommand(String name) { - return "echo %" + name + "%"; + public String getPrintVariableCommand(String prefix, String name) { + return "call echo " + prefix + "^%" + name + "^%"; } @Override public String getEchoCommand(String s, boolean toErrorStream) { - return toErrorStream ? "(echo " + s + ")1>&2" : "echo " + s; + return "(echo " + s + (toErrorStream ? ")1>&2" : ")"); + } + + @Override + public String getScriptEchoCommand(String s) { + return ("@echo off\r\nset \"echov=" + escapeStringValue(s) + "\"\r\necho %echov%"); } @Override @@ -93,12 +98,11 @@ public class ShellTypes { @Override public String getOpenWithInitFileCommand(String file) { - return String.format("%s /V:on %s \"%s\"", getExecutable(), "/C", file); + return String.format("%s %s \"%s\"", getExecutable(), "/C", file); } - @Override - public String escape(String input) { - return input; + public String escapeStringValue(String input) { + return input.replaceAll("[&^|<>\"]", "^$0"); } @Override @@ -130,7 +134,7 @@ public class ShellTypes { @Override public String getExitCodeVariable() { - return "!errorlevel!"; + return "errorlevel"; } @Override @@ -140,12 +144,12 @@ public class ShellTypes { @Override public List openCommand() { - return List.of("cmd", "/V:on"); + return List.of("cmd"); } @Override public String switchTo(String cmd) { - return "cmd.exe /V:on /c " + cmd; + return "cmd.exe /V:on /c '" + cmd + "'"; } @Override @@ -192,7 +196,7 @@ public class ShellTypes { @Override public String getDisplayName() { - return "cmd"; + return "cmd.exe"; } @Override @@ -228,13 +232,13 @@ public class ShellTypes { } @Override - public String getPrintVariableCommand(String name) { - return "echo %" + name + "%"; + public String getPrintVariableCommand(String prefix, String name) { + return "echo \"" + escapeStringValue(prefix) + "$" + escapeStringValue(name) + "\""; } @Override public String getSetVariableCommand(String variableName, String value) { - return "$env:" + variableName + " = \"" + value + "\""; + return "$env:" + variableName + " = \"" + escapeStringValue(value) + "\""; } @Override @@ -284,9 +288,8 @@ public class ShellTypes { return String.format("%s -ExecutionPolicy Bypass -File \"%s\"", getExecutable(), file); } - @Override - public String escape(String input) { - return input; + public String escapeStringValue(String input) { + return input.replaceAll("[\"]", "`$0"); } @Override @@ -304,7 +307,7 @@ public class ShellTypes { @Override public String getExitCodeVariable() { - return "$LASTEXITCODE"; + return "LASTEXITCODE"; } @Override @@ -323,7 +326,7 @@ public class ShellTypes { @Override public String switchTo(String cmd) { - return "powershell.exe -Command " + cmd; + return "powershell.exe -Command '" + cmd + "'"; } @Override @@ -402,7 +405,7 @@ public class ShellTypes { // Force sudo to always query for a password by using the -k switch return "sudo -k -p \"\" -S < <(echo \"" + control.getElevationPassword() + "\") -- " - + escape(command); + + command; } @Override @@ -418,8 +421,8 @@ public class ShellTypes { } @Override - public String getPrintVariableCommand(String name) { - return "echo $" + name; + public String getPrintVariableCommand(String prefix, String name) { + return "echo " + prefix + "$" + name; } @Override @@ -437,11 +440,6 @@ public class ShellTypes { return String.format("%s -i -l \"%s\"", getExecutable(), file); } - @Override - public String escape(String input) { - return input.replace("$", "\\$"); - } - @Override public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception { if (control.getElevationPassword() == null) { @@ -450,13 +448,13 @@ public class ShellTypes { } // For sudo to always query for a password by using the -k switch - control.executeCommand("sudo -p \"\" -k -S -- " + escape(command)); + control.executeCommand("sudo -p \"\" -k -S -- " + command); control.writeLine(control.getElevationPassword().getSecretValue()); } @Override public String getExitCodeVariable() { - return "$?"; + return "?"; } @Override @@ -486,7 +484,7 @@ public class ShellTypes { @Override public String switchTo(String cmd) { - return getName() + " -c \"" + cmd + "\""; + return getName() + " -c '" + cmd + "'"; } @Override diff --git a/core/src/main/java/io/xpipe/core/store/MachineStore.java b/core/src/main/java/io/xpipe/core/store/MachineStore.java index 8868bf6a..62f63521 100644 --- a/core/src/main/java/io/xpipe/core/store/MachineStore.java +++ b/core/src/main/java/io/xpipe/core/store/MachineStore.java @@ -1,28 +1,14 @@ package io.xpipe.core.store; -import io.xpipe.core.process.ShellProcessControl; - import java.io.InputStream; import java.io.OutputStream; public interface MachineStore extends FileSystemStore, ShellStore { - @Override - default void validate() throws Exception { - try (ShellProcessControl pc = create().start()) {} - } - public default boolean isLocal() { return false; } - public default String queryMachineName() throws Exception { - try (var pc = create().start()) { - var operatingSystem = pc.getOsType(); - return operatingSystem.determineOperatingSystemName(pc); - } - } - @Override public default InputStream openInput(String file) throws Exception { return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(proc.getOsType().normalizeFileName(file))) diff --git a/core/src/main/java/io/xpipe/core/store/ShellStore.java b/core/src/main/java/io/xpipe/core/store/ShellStore.java index e855e726..0da5b9d2 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/ShellStore.java @@ -21,4 +21,15 @@ public interface ShellStore extends DataStore { return pc.getShellType(); } } + @Override + default void validate() throws Exception { + try (ShellProcessControl pc = create().start()) {} + } + + public default String queryMachineName() throws Exception { + try (var pc = create().start()) { + var operatingSystem = pc.getOsType(); + return operatingSystem.determineOperatingSystemName(pc); + } + } } diff --git a/extension/src/main/java/io/xpipe/extension/XPipeProxy.java b/extension/src/main/java/io/xpipe/extension/XPipeProxy.java index a0112571..6747d438 100644 --- a/extension/src/main/java/io/xpipe/extension/XPipeProxy.java +++ b/extension/src/main/java/io/xpipe/extension/XPipeProxy.java @@ -11,6 +11,10 @@ import java.io.IOException; public class XPipeProxy { public static void checkSupport(ShellStore store) throws Exception { + if (store == null || ShellStore.isLocal(store)) { + return; + } + var version = XPipeDaemon.getInstance().getVersion(); try (ShellProcessControl s = store.create().start()) { var defaultInstallationExecutable = FileNames.join( diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ProxyChoiceComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ProxyChoiceComp.java index 932d04f7..b7f73f8f 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ProxyChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ProxyChoiceComp.java @@ -1,30 +1,28 @@ package io.xpipe.extension.fxcomps.impl; import io.xpipe.core.store.ShellStore; -import io.xpipe.extension.XPipeProxy; import io.xpipe.extension.fxcomps.SimpleComp; import io.xpipe.extension.util.SimpleValidator; import io.xpipe.extension.util.Validatable; import io.xpipe.extension.util.Validator; import javafx.beans.property.Property; import javafx.scene.layout.Region; -import net.synedra.validatorfx.Check; public class ProxyChoiceComp extends SimpleComp implements Validatable { private final Property selected; private final Validator validator = new SimpleValidator(); - private final Check check; + // private final Check check; public ProxyChoiceComp(Property selected) { this.selected = selected; - check = Validator.exceptionWrapper(validator, selected, () -> XPipeProxy.checkSupport(selected.getValue())); + // check = Validator.exceptionWrapper(validator, selected, () -> XPipeProxy.checkSupport(selected.getValue())); } @Override protected Region createSimple() { - var choice = new ShellStoreChoiceComp<>(selected, ShellStore.class, shellStore -> true, shellStore -> true); - choice.apply(struc -> check.decorates(struc.get())); + var choice = new ShellStoreChoiceComp<>(null, selected, ShellStore.class, shellStore -> true); + // choice.apply(struc -> check.decorates(struc.get())); return choice.createRegion(); } diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ShellStoreChoiceComp.java b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ShellStoreChoiceComp.java index 4c5af3de..0f1e37c6 100644 --- a/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ShellStoreChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/fxcomps/impl/ShellStoreChoiceComp.java @@ -25,10 +25,10 @@ TODO: Integrate store validation more into this comp. @AllArgsConstructor public class ShellStoreChoiceComp extends SimpleComp { + private final T self; private final Property selected; private final Class storeClass; private final Predicate applicableCheck; - private final Predicate supportCheck; private Region createGraphic(T s) { var provider = DataStoreProviders.byStore(s); @@ -80,6 +80,7 @@ public class ShellStoreChoiceComp extends SimpleComp { var available = Stream.concat( Stream.of(new LocalStore()), XPipeDaemon.getInstance().getNamedStores().stream() + .filter(s -> s != self) .filter(s -> storeClass.isAssignableFrom(s.getClass()) && applicableCheck.test((T) s)) .map(s -> (ShellStore) s)) .toList(); diff --git a/extension/src/main/java/io/xpipe/extension/util/DataStoreFormatter.java b/extension/src/main/java/io/xpipe/extension/util/DataStoreFormatter.java index 437d8fdd..b251a79c 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DataStoreFormatter.java +++ b/extension/src/main/java/io/xpipe/extension/util/DataStoreFormatter.java @@ -8,9 +8,21 @@ import java.util.function.IntFunction; public class DataStoreFormatter { + public static String formatSubHost(IntFunction func, DataStore at, int length) { + var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) + ? XPipeDaemon.getInstance().getStoreName(at).orElse(null) + : null; + if (atString == null) { + return func.apply(length); + } + + var fileString = func.apply(length - atString.length() - 1); + return String.format("%s/%s", atString, fileString); + } + public static String formatAtHost(IntFunction func, DataStore at, int length) { var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) - ? DataStoreProviders.byStore(at).toSummaryString(at, length) + ? XPipeDaemon.getInstance().getStoreName(at).orElse(null) : null; if (atString == null) { return func.apply(length); @@ -20,7 +32,22 @@ public class DataStoreFormatter { return String.format("%s @ %s", fileString, atString); } - public static String format(DataStore input, int length) { + public static String formatViaProxy(IntFunction func, DataStore at, int length) { + var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) + ? XPipeDaemon.getInstance().getStoreName(at).orElse(null) + : null; + if (atString == null) { + return func.apply(length); + } + + var fileString = func.apply(length - atString.length() - 4); + return String.format("%s -> %s", atString, fileString); + } + + public static String toName(DataStore input) { + return toName(input, Integer.MAX_VALUE); + } + public static String toName(DataStore input, int length) { var named = XPipeDaemon.getInstance().getStoreName(input); if (named.isPresent()) { return cut(named.get(), length); @@ -29,6 +56,11 @@ public class DataStoreFormatter { return DataStoreProviders.byStore(input).toSummaryString(input, length); } + public static String split(String left, String separator, String right, int length) { + var half = (length / 2) - separator.length(); + return cut(left, half) + separator + cut(right, length - half); + } + public static String cut(String input, int length) { if (input == null) { return ""; @@ -54,7 +86,7 @@ public class DataStoreFormatter { var region = split[2]; var lengthShare = (length - 3) / 2; return String.format( - "%s @ %s", + "%s.%s", DataStoreFormatter.cut(name, lengthShare), DataStoreFormatter.cut(region, length - lengthShare)); } diff --git a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties index 41c1efff..4cb7ac80 100644 --- a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties +++ b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties @@ -13,6 +13,7 @@ missingStore=$NAME$ does not exist namedHostFeatureUnsupported=$HOST$ does not support this feature namedHostNotActive=$HOST$ is not active noInformationAvailable=No information available +localMachine=Local Machine input=Input output=Output inout=Input and Output