Fixes for shells

This commit is contained in:
Christopher Schnick 2022-12-07 21:55:58 +01:00
parent c37ab93c13
commit 37db891366
11 changed files with 107 additions and 73 deletions

View file

@ -47,7 +47,7 @@ public interface OsType {
@Override @Override
public String getTempDirectory(ShellProcessControl pc) throws Exception { 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 @Override
@ -111,7 +111,7 @@ public interface OsType {
@Override @Override
public String determineOperatingSystemName(ShellProcessControl pc) throws Exception { public String determineOperatingSystemName(ShellProcessControl pc) throws Exception {
try (CommandProcessControl c = try (CommandProcessControl c =
pc.command(ShellTypes.SH, "lsb_release -a").start()) { pc.command("lsb_release -a").start()) {
var text = c.readOnlyStdout(); var text = c.readOnlyStdout();
if (c.getExitCode() == 0) { if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", null); return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", null);
@ -119,7 +119,7 @@ public interface OsType {
} }
try (CommandProcessControl c = try (CommandProcessControl c =
pc.command(ShellTypes.SH, "cat /etc/*release").start()) { pc.command("cat /etc/*release").start()) {
var text = c.readOnlyStdout(); var text = c.readOnlyStdout();
if (c.getExitCode() == 0) { if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", null); return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", null);
@ -127,7 +127,7 @@ public interface OsType {
} }
String type = "Unknown"; 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(); var text = c.readOnlyStdout();
if (c.getExitCode() == 0) { if (c.getExitCode() == 0) {
type = text.strip(); type = text.strip();
@ -135,7 +135,7 @@ public interface OsType {
} }
String version = "?"; String version = "?";
try (CommandProcessControl c = pc.command(ShellTypes.SH, "uname -r").start()) { try (CommandProcessControl c = pc.command("uname -r").start()) {
var text = c.readOnlyStdout(); var text = c.readOnlyStdout();
if (c.getExitCode() == 0) { if (c.getExitCode() == 0) {
version = text.strip(); version = text.strip();

View file

@ -7,7 +7,6 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
public interface ShellProcessControl extends ProcessControl { public interface ShellProcessControl extends ProcessControl {
@ -30,7 +29,9 @@ public interface ShellProcessControl extends ProcessControl {
} }
default String executeSimpleCommand(ShellType type, String command) throws Exception { 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; void restart() throws Exception;
@ -53,13 +54,8 @@ public interface ShellProcessControl extends ProcessControl {
return subShell(type.openCommand()).elevation(getElevationPassword()); return subShell(type.openCommand()).elevation(getElevationPassword());
} }
default CommandProcessControl command(@NonNull ShellType type, String command) {
return command(type.switchTo(command));
}
default ShellProcessControl subShell(@NonNull List<String> command) { default ShellProcessControl subShell(@NonNull List<String> command) {
return subShell( return subShell(shellProcessControl -> shellProcessControl.getShellType().flatten(command));
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
} }
default ShellProcessControl subShell(@NonNull String command) { default ShellProcessControl subShell(@NonNull String command) {

View file

@ -23,16 +23,15 @@ public interface ShellType {
String getOpenWithInitFileCommand(String file); String getOpenWithInitFileCommand(String file);
default String flatten(List<String> command) { default String flatten(List<String> 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) { default String joinCommands(String... s) {
return String.join(getConcatenationOperator(), s); return String.join(getConcatenationOperator(), s);
} }
String escape(String input);
void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception; void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception;
default String getExitCommand() { default String getExitCommand() {
@ -57,13 +56,21 @@ public interface ShellType {
String elevateConsoleCommand(ShellProcessControl control, String command); String elevateConsoleCommand(ShellProcessControl control, String command);
default String getScriptEchoCommand(String s) {
return getEchoCommand(s, false);
}
String getEchoCommand(String s, boolean toErrorStream); String getEchoCommand(String s, boolean toErrorStream);
String queryShellProcessId(ShellProcessControl control) throws Exception; String queryShellProcessId(ShellProcessControl control) throws Exception;
String getSetVariableCommand(String variableName, String value); 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<String> openCommand(); List<String> openCommand();

View file

@ -50,17 +50,22 @@ public class ShellTypes {
@Override @Override
public String getSetVariableCommand(String variableName, String value) { public String getSetVariableCommand(String variableName, String value) {
return ("set \"" + variableName + "=" + value + "\"").replaceAll("!", "^!"); return ("set \"" + variableName + "=" + value.replaceAll("\"", "^$0") + "\"");
} }
@Override @Override
public String getPrintVariableCommand(String name) { public String getPrintVariableCommand(String prefix, String name) {
return "echo %" + name + "%"; return "call echo " + prefix + "^%" + name + "^%";
} }
@Override @Override
public String getEchoCommand(String s, boolean toErrorStream) { 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 @Override
@ -93,12 +98,11 @@ public class ShellTypes {
@Override @Override
public String getOpenWithInitFileCommand(String file) { 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 escapeStringValue(String input) {
public String escape(String input) { return input.replaceAll("[&^|<>\"]", "^$0");
return input;
} }
@Override @Override
@ -130,7 +134,7 @@ public class ShellTypes {
@Override @Override
public String getExitCodeVariable() { public String getExitCodeVariable() {
return "!errorlevel!"; return "errorlevel";
} }
@Override @Override
@ -140,12 +144,12 @@ public class ShellTypes {
@Override @Override
public List<String> openCommand() { public List<String> openCommand() {
return List.of("cmd", "/V:on"); return List.of("cmd");
} }
@Override @Override
public String switchTo(String cmd) { public String switchTo(String cmd) {
return "cmd.exe /V:on /c " + cmd; return "cmd.exe /V:on /c '" + cmd + "'";
} }
@Override @Override
@ -192,7 +196,7 @@ public class ShellTypes {
@Override @Override
public String getDisplayName() { public String getDisplayName() {
return "cmd"; return "cmd.exe";
} }
@Override @Override
@ -228,13 +232,13 @@ public class ShellTypes {
} }
@Override @Override
public String getPrintVariableCommand(String name) { public String getPrintVariableCommand(String prefix, String name) {
return "echo %" + name + "%"; return "echo \"" + escapeStringValue(prefix) + "$" + escapeStringValue(name) + "\"";
} }
@Override @Override
public String getSetVariableCommand(String variableName, String value) { public String getSetVariableCommand(String variableName, String value) {
return "$env:" + variableName + " = \"" + value + "\""; return "$env:" + variableName + " = \"" + escapeStringValue(value) + "\"";
} }
@Override @Override
@ -284,9 +288,8 @@ public class ShellTypes {
return String.format("%s -ExecutionPolicy Bypass -File \"%s\"", getExecutable(), file); return String.format("%s -ExecutionPolicy Bypass -File \"%s\"", getExecutable(), file);
} }
@Override public String escapeStringValue(String input) {
public String escape(String input) { return input.replaceAll("[\"]", "`$0");
return input;
} }
@Override @Override
@ -304,7 +307,7 @@ public class ShellTypes {
@Override @Override
public String getExitCodeVariable() { public String getExitCodeVariable() {
return "$LASTEXITCODE"; return "LASTEXITCODE";
} }
@Override @Override
@ -323,7 +326,7 @@ public class ShellTypes {
@Override @Override
public String switchTo(String cmd) { public String switchTo(String cmd) {
return "powershell.exe -Command " + cmd; return "powershell.exe -Command '" + cmd + "'";
} }
@Override @Override
@ -402,7 +405,7 @@ public class ShellTypes {
// Force sudo to always query for a password by using the -k switch // Force sudo to always query for a password by using the -k switch
return "sudo -k -p \"\" -S < <(echo \"" + control.getElevationPassword() + "\") -- " return "sudo -k -p \"\" -S < <(echo \"" + control.getElevationPassword() + "\") -- "
+ escape(command); + command;
} }
@Override @Override
@ -418,8 +421,8 @@ public class ShellTypes {
} }
@Override @Override
public String getPrintVariableCommand(String name) { public String getPrintVariableCommand(String prefix, String name) {
return "echo $" + name; return "echo " + prefix + "$" + name;
} }
@Override @Override
@ -437,11 +440,6 @@ public class ShellTypes {
return String.format("%s -i -l \"%s\"", getExecutable(), file); return String.format("%s -i -l \"%s\"", getExecutable(), file);
} }
@Override
public String escape(String input) {
return input.replace("$", "\\$");
}
@Override @Override
public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception { public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception {
if (control.getElevationPassword() == null) { if (control.getElevationPassword() == null) {
@ -450,13 +448,13 @@ public class ShellTypes {
} }
// For sudo to always query for a password by using the -k switch // 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()); control.writeLine(control.getElevationPassword().getSecretValue());
} }
@Override @Override
public String getExitCodeVariable() { public String getExitCodeVariable() {
return "$?"; return "?";
} }
@Override @Override
@ -486,7 +484,7 @@ public class ShellTypes {
@Override @Override
public String switchTo(String cmd) { public String switchTo(String cmd) {
return getName() + " -c \"" + cmd + "\""; return getName() + " -c '" + cmd + "'";
} }
@Override @Override

View file

@ -1,28 +1,14 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.process.ShellProcessControl;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public interface MachineStore extends FileSystemStore, ShellStore { public interface MachineStore extends FileSystemStore, ShellStore {
@Override
default void validate() throws Exception {
try (ShellProcessControl pc = create().start()) {}
}
public default boolean isLocal() { public default boolean isLocal() {
return false; return false;
} }
public default String queryMachineName() throws Exception {
try (var pc = create().start()) {
var operatingSystem = pc.getOsType();
return operatingSystem.determineOperatingSystemName(pc);
}
}
@Override @Override
public default InputStream openInput(String file) throws Exception { public default InputStream openInput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(proc.getOsType().normalizeFileName(file))) return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(proc.getOsType().normalizeFileName(file)))

View file

@ -21,4 +21,15 @@ public interface ShellStore extends DataStore {
return pc.getShellType(); 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);
}
}
} }

View file

@ -11,6 +11,10 @@ import java.io.IOException;
public class XPipeProxy { public class XPipeProxy {
public static void checkSupport(ShellStore store) throws Exception { public static void checkSupport(ShellStore store) throws Exception {
if (store == null || ShellStore.isLocal(store)) {
return;
}
var version = XPipeDaemon.getInstance().getVersion(); var version = XPipeDaemon.getInstance().getVersion();
try (ShellProcessControl s = store.create().start()) { try (ShellProcessControl s = store.create().start()) {
var defaultInstallationExecutable = FileNames.join( var defaultInstallationExecutable = FileNames.join(

View file

@ -1,30 +1,28 @@
package io.xpipe.extension.fxcomps.impl; package io.xpipe.extension.fxcomps.impl;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.XPipeProxy;
import io.xpipe.extension.fxcomps.SimpleComp; import io.xpipe.extension.fxcomps.SimpleComp;
import io.xpipe.extension.util.SimpleValidator; import io.xpipe.extension.util.SimpleValidator;
import io.xpipe.extension.util.Validatable; import io.xpipe.extension.util.Validatable;
import io.xpipe.extension.util.Validator; import io.xpipe.extension.util.Validator;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import net.synedra.validatorfx.Check;
public class ProxyChoiceComp extends SimpleComp implements Validatable { public class ProxyChoiceComp extends SimpleComp implements Validatable {
private final Property<ShellStore> selected; private final Property<ShellStore> selected;
private final Validator validator = new SimpleValidator(); private final Validator validator = new SimpleValidator();
private final Check check; // private final Check check;
public ProxyChoiceComp(Property<ShellStore> selected) { public ProxyChoiceComp(Property<ShellStore> selected) {
this.selected = selected; this.selected = selected;
check = Validator.exceptionWrapper(validator, selected, () -> XPipeProxy.checkSupport(selected.getValue())); // check = Validator.exceptionWrapper(validator, selected, () -> XPipeProxy.checkSupport(selected.getValue()));
} }
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var choice = new ShellStoreChoiceComp<>(selected, ShellStore.class, shellStore -> true, shellStore -> true); var choice = new ShellStoreChoiceComp<>(null, selected, ShellStore.class, shellStore -> true);
choice.apply(struc -> check.decorates(struc.get())); // choice.apply(struc -> check.decorates(struc.get()));
return choice.createRegion(); return choice.createRegion();
} }

View file

@ -25,10 +25,10 @@ TODO: Integrate store validation more into this comp.
@AllArgsConstructor @AllArgsConstructor
public class ShellStoreChoiceComp<T extends ShellStore> extends SimpleComp { public class ShellStoreChoiceComp<T extends ShellStore> extends SimpleComp {
private final T self;
private final Property<T> selected; private final Property<T> selected;
private final Class<T> storeClass; private final Class<T> storeClass;
private final Predicate<T> applicableCheck; private final Predicate<T> applicableCheck;
private final Predicate<T> supportCheck;
private Region createGraphic(T s) { private Region createGraphic(T s) {
var provider = DataStoreProviders.byStore(s); var provider = DataStoreProviders.byStore(s);
@ -80,6 +80,7 @@ public class ShellStoreChoiceComp<T extends ShellStore> extends SimpleComp {
var available = Stream.concat( var available = Stream.concat(
Stream.of(new LocalStore()), Stream.of(new LocalStore()),
XPipeDaemon.getInstance().getNamedStores().stream() XPipeDaemon.getInstance().getNamedStores().stream()
.filter(s -> s != self)
.filter(s -> storeClass.isAssignableFrom(s.getClass()) && applicableCheck.test((T) s)) .filter(s -> storeClass.isAssignableFrom(s.getClass()) && applicableCheck.test((T) s))
.map(s -> (ShellStore) s)) .map(s -> (ShellStore) s))
.toList(); .toList();

View file

@ -8,9 +8,21 @@ import java.util.function.IntFunction;
public class DataStoreFormatter { public class DataStoreFormatter {
public static String formatSubHost(IntFunction<String> 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<String> func, DataStore at, int length) { public static String formatAtHost(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? DataStoreProviders.byStore(at).toSummaryString(at, length) ? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
: null; : null;
if (atString == null) { if (atString == null) {
return func.apply(length); return func.apply(length);
@ -20,7 +32,22 @@ public class DataStoreFormatter {
return String.format("%s @ %s", fileString, atString); return String.format("%s @ %s", fileString, atString);
} }
public static String format(DataStore input, int length) { public static String formatViaProxy(IntFunction<String> 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); var named = XPipeDaemon.getInstance().getStoreName(input);
if (named.isPresent()) { if (named.isPresent()) {
return cut(named.get(), length); return cut(named.get(), length);
@ -29,6 +56,11 @@ public class DataStoreFormatter {
return DataStoreProviders.byStore(input).toSummaryString(input, length); 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) { public static String cut(String input, int length) {
if (input == null) { if (input == null) {
return ""; return "";
@ -54,7 +86,7 @@ public class DataStoreFormatter {
var region = split[2]; var region = split[2];
var lengthShare = (length - 3) / 2; var lengthShare = (length - 3) / 2;
return String.format( return String.format(
"%s @ %s", "%s.%s",
DataStoreFormatter.cut(name, lengthShare), DataStoreFormatter.cut(region, length - lengthShare)); DataStoreFormatter.cut(name, lengthShare), DataStoreFormatter.cut(region, length - lengthShare));
} }

View file

@ -13,6 +13,7 @@ missingStore=$NAME$ does not exist
namedHostFeatureUnsupported=$HOST$ does not support this feature namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available noInformationAvailable=No information available
localMachine=Local Machine
input=Input input=Input
output=Output output=Output
inout=Input and Output inout=Input and Output