mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Refactor shells
This commit is contained in:
parent
b14393134f
commit
a6e9b78b09
16 changed files with 186 additions and 97 deletions
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.core.impl;
|
||||
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.process.ShellTypes;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ public class OutputStreamStore implements StreamDataStore {
|
|||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContentExclusivelyAccessible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFlow getFlow() {
|
||||
return DataFlow.OUTPUT;
|
||||
|
|
|
@ -7,6 +7,8 @@ import java.util.function.Consumer;
|
|||
|
||||
public interface CommandProcessControl extends ProcessControl {
|
||||
|
||||
@Override
|
||||
public CommandProcessControl sensitive();
|
||||
CommandProcessControl complex();
|
||||
|
||||
default InputStream startExternalStdout() throws Exception {
|
||||
|
@ -64,9 +66,7 @@ public interface CommandProcessControl extends ProcessControl {
|
|||
|
||||
String readOnlyStdout() throws Exception;
|
||||
|
||||
public default void discardOrThrow() throws Exception {
|
||||
readOrThrow();
|
||||
}
|
||||
public void discardOrThrow() throws Exception;
|
||||
|
||||
void accumulateStdout(Consumer<String> con);
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import java.nio.charset.Charset;
|
|||
|
||||
public interface ProcessControl extends Closeable, AutoCloseable {
|
||||
|
||||
ProcessControl sensitive();
|
||||
|
||||
String prepareTerminalOpen() throws Exception;
|
||||
|
||||
void closeStdin() throws IOException;
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.core.charsetter.NewLine;
|
|||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@JsonTypeInfo(
|
||||
|
@ -15,15 +16,11 @@ public interface ShellType {
|
|||
|
||||
String getScriptFileEnding();
|
||||
|
||||
default String commandWithVariable(String key, String value, String command) {
|
||||
return joinCommands(getSetVariableCommand(key, value), command);
|
||||
}
|
||||
String addInlineVariablesToCommand(Map<String, String> variables, String command);
|
||||
|
||||
String getPauseCommand();
|
||||
|
||||
String createInitFileContent(String command);
|
||||
|
||||
String getTerminalFileOpenCommand(String file);
|
||||
String prepareScriptContent(String content);
|
||||
|
||||
default String flatten(List<String> command) {
|
||||
return command.stream()
|
||||
|
@ -35,12 +32,6 @@ public interface ShellType {
|
|||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
default String joinCommands(String... s) {
|
||||
return String.join(getConcatenationOperator(), s);
|
||||
}
|
||||
|
||||
void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception;
|
||||
|
||||
default String getExitCommand() {
|
||||
return "exit";
|
||||
}
|
||||
|
@ -61,11 +52,11 @@ public interface ShellType {
|
|||
return getEchoCommand(s, false);
|
||||
}
|
||||
|
||||
String getSetVariableCommand(String variable, String value);
|
||||
|
||||
|
||||
String getEchoCommand(String s, boolean toErrorStream);
|
||||
|
||||
String queryShellProcessId(ShellProcessControl control) throws Exception;
|
||||
|
||||
String getSetVariableCommand(String variableName, String value);
|
||||
|
||||
default String getPrintVariableCommand(String name) {
|
||||
return getPrintVariableCommand("", name);
|
||||
|
@ -79,19 +70,21 @@ public interface ShellType {
|
|||
|
||||
List<String> executeCommandListWithShell(String cmd);
|
||||
|
||||
List<String> createMkdirsCommand(String dirs);
|
||||
List<String> getMkdirsCommand(String dirs);
|
||||
|
||||
String createFileReadCommand(String file);
|
||||
String getFileReadCommand(String file);
|
||||
|
||||
String createFileWriteCommand(String file);
|
||||
String getStreamFileWriteCommand(String file);
|
||||
|
||||
String createFileDeleteCommand(String file);
|
||||
String getSimpleFileWriteCommand(String content, String file);
|
||||
|
||||
String createFileExistsCommand(String file);
|
||||
String getFileDeleteCommand(String file);
|
||||
|
||||
String createFileTouchCommand(String file);
|
||||
String getFileExistsCommand(String file);
|
||||
|
||||
String createWhichCommand(String executable);
|
||||
String getFileTouchCommand(String file);
|
||||
|
||||
String getWhichCommand(String executable);
|
||||
|
||||
Charset determineCharset(ShellProcessControl control) throws Exception;
|
||||
|
||||
|
@ -103,5 +96,5 @@ public interface ShellType {
|
|||
|
||||
String getExecutable();
|
||||
|
||||
boolean echoesInput();
|
||||
boolean doesRepeatInput();
|
||||
}
|
||||
|
|
|
@ -12,21 +12,21 @@ public interface MachineStore extends FileSystemStore, ShellStore {
|
|||
@Override
|
||||
public default InputStream openInput(String file) throws Exception {
|
||||
return create().command(proc -> proc.getShellType()
|
||||
.createFileReadCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getFileReadCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.startExternalStdout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public default OutputStream openOutput(String file) throws Exception {
|
||||
return create().command(proc -> proc.getShellType()
|
||||
.createFileWriteCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getStreamFileWriteCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.startExternalStdin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public default boolean exists(String file) throws Exception {
|
||||
try (var pc = create().command(proc -> proc.getShellType()
|
||||
.createFileExistsCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getFileExistsCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.start()) {
|
||||
return pc.discardAndCheckExit();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public interface MachineStore extends FileSystemStore, ShellStore {
|
|||
public default boolean mkdirs(String file) throws Exception {
|
||||
try (var pc = create().command(proc -> proc.getShellType()
|
||||
.flatten(proc.getShellType()
|
||||
.createMkdirsCommand(proc.getOsType().normalizeFileName(file))))
|
||||
.getMkdirsCommand(proc.getOsType().normalizeFileName(file))))
|
||||
.start()) {
|
||||
return pc.discardAndCheckExit();
|
||||
}
|
||||
|
|
|
@ -105,6 +105,6 @@ public class Deobfuscator {
|
|||
}
|
||||
|
||||
var t = ShellTypes.getPlatformDefault();
|
||||
return LocalProcess.executeSimpleBooleanCommand(t.createWhichCommand("retrace." + t.getScriptFileEnding()));
|
||||
return LocalProcess.executeSimpleBooleanCommand(t.getWhichCommand("retrace." + t.getScriptFileEnding()));
|
||||
}
|
||||
}
|
||||
|
|
53
core/src/main/java/io/xpipe/core/util/XPipeSession.java
Normal file
53
core/src/main/java/io/xpipe/core/util/XPipeSession.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
package io.xpipe.core.util;
|
||||
|
||||
import io.xpipe.core.process.OsType;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.UUID;
|
||||
|
||||
@Value
|
||||
public class XPipeSession {
|
||||
|
||||
boolean isNewSystemSession;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on every X-Pipe restart.
|
||||
*/
|
||||
UUID sessionId;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on every X-Pipe update.
|
||||
*/
|
||||
UUID buildSessionId;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on system restarts.
|
||||
*/
|
||||
UUID systemSessionId;
|
||||
|
||||
private static XPipeSession INSTANCE;
|
||||
|
||||
public static void init(UUID buildSessionId) throws Exception {
|
||||
var sessionFile = XPipeTempDirectory.getLocal().resolve("xpipe_session");
|
||||
var isNew = !Files.exists(sessionFile);
|
||||
var systemSessionId = isNew ? UUID.randomUUID() : UUID.fromString(Files.readString(sessionFile));
|
||||
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
var pf = Path.of("C:\\pagefile.sys");
|
||||
BasicFileAttributes attr = Files.readAttributes(pf, BasicFileAttributes.class);
|
||||
var timeUuid = UUID.nameUUIDFromBytes(attr.creationTime().toInstant().toString().getBytes());
|
||||
isNew = isNew && timeUuid.equals(systemSessionId);
|
||||
systemSessionId = timeUuid;
|
||||
}
|
||||
|
||||
Files.writeString(sessionFile, systemSessionId.toString());
|
||||
INSTANCE = new XPipeSession(isNew, UUID.randomUUID(), buildSessionId, systemSessionId);
|
||||
}
|
||||
|
||||
public static XPipeSession get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@ public class XPipeTempDirectory {
|
|||
var base = proc.getOsType().getTempDirectory(proc);
|
||||
var dir = FileNames.join(base, "xpipe");
|
||||
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().createFileExistsCommand(dir))) {
|
||||
proc.executeSimpleCommand(proc.getShellType().flatten(proc.getShellType().createMkdirsCommand(dir)), "Unable to access or create temporary directory " + 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);
|
||||
|
||||
if (proc.getOsType().equals(OsType.LINUX) || proc.getOsType().equals(OsType.MAC)) {
|
||||
proc.executeSimpleCommand("(chmod -f 777 \"" + dir + "\"");
|
||||
|
@ -33,7 +33,7 @@ public class XPipeTempDirectory {
|
|||
|
||||
public static void clear(ShellProcessControl proc) throws Exception {
|
||||
var dir = get(proc);
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().createFileDeleteCommand(dir))) {
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileDeleteCommand(dir))) {
|
||||
throw new IOException("Unable to delete temporary directory " + dir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.io.IOException;
|
|||
public class ApplicationHelper {
|
||||
|
||||
public static boolean isInPath(ShellProcessControl processControl, String executable) throws Exception {
|
||||
return processControl.executeBooleanSimpleCommand(processControl.getShellType().createWhichCommand(executable));
|
||||
return processControl.executeBooleanSimpleCommand(processControl.getShellType().getWhichCommand(executable));
|
||||
}
|
||||
|
||||
public static void checkSupport(ShellProcessControl processControl, String executable, String displayName) throws Exception {
|
||||
|
|
|
@ -4,10 +4,13 @@ import io.xpipe.api.DataSource;
|
|||
import io.xpipe.beacon.BeaconDaemonController;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.extension.XPipeServiceProviders;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class DaemonExtensionTest extends ExtensionTest {
|
||||
|
||||
public static DataSource getSource(String type, DataStore store) {
|
||||
|
@ -26,6 +29,7 @@ public class DaemonExtensionTest extends ExtensionTest {
|
|||
public static void setup() throws Exception {
|
||||
JacksonMapper.initModularized(ModuleLayer.boot());
|
||||
XPipeServiceProviders.load(ModuleLayer.boot());
|
||||
XPipeSession.init(UUID.randomUUID());
|
||||
BeaconDaemonController.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.XPipeTempDirectory;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ExecScriptHelper {
|
||||
|
||||
public static int getConnectionHash(String command) {
|
||||
return Math.abs(Objects.hash(command));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createLocalExecScript(String content) {
|
||||
try (var l = ShellStore.local().create().start()) {
|
||||
return createExecScript(l, content);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createExecScript(ShellProcessControl processControl, String content) {
|
||||
var fileName = "exec-" + getConnectionHash(content);
|
||||
content = processControl.getShellType().createInitFileContent(content);
|
||||
var temp = XPipeTempDirectory.get(processControl);
|
||||
var file = FileNames.join(
|
||||
temp, fileName + "." + processControl.getShellType().getScriptFileEnding());
|
||||
|
||||
if (processControl.executeBooleanSimpleCommand(processControl.getShellType().createFileExistsCommand(file))) {
|
||||
return file;
|
||||
}
|
||||
|
||||
try (var c = processControl.command(processControl.getShellType()
|
||||
.joinCommands(
|
||||
processControl.getShellType().createFileWriteCommand(file),
|
||||
processControl.getShellType().getMakeExecutableCommand(file)
|
||||
))
|
||||
.start()) {
|
||||
c.discardOut();
|
||||
c.discardErr();
|
||||
c.getStdin().write(content.getBytes(processControl.getCharset()));
|
||||
c.closeStdin();
|
||||
}
|
||||
|
||||
processControl.restart();
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.extension.XPipeServiceProviders;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LocalExtensionTest extends ExtensionTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
JacksonMapper.initModularized(ModuleLayer.boot());
|
||||
XPipeServiceProviders.load(ModuleLayer.boot());
|
||||
XPipeSession.init(UUID.randomUUID());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.process.ShellType;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.core.util.XPipeTempDirectory;
|
||||
import io.xpipe.extension.event.TrackEvent;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ScriptHelper {
|
||||
|
||||
public static int getConnectionHash(String command) {
|
||||
return Math.abs(Objects.hash(command, XPipeSession.get().getSystemSessionId()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createLocalExecScript(String content) {
|
||||
try (var l = ShellStore.local().create().start()) {
|
||||
return createExecScript(l, content, false);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createExecScript(ShellProcessControl processControl, String content, boolean restart) {
|
||||
var fileName = "exec-" + getConnectionHash(content);
|
||||
ShellType type = processControl.getShellType();
|
||||
var temp = XPipeTempDirectory.get(processControl);
|
||||
var file = FileNames.join(temp, fileName + "." + type.getScriptFileEnding());
|
||||
return createExecScript(processControl, file, content, restart);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static String createExecScript(ShellProcessControl processControl, String file, String content, boolean restart) {
|
||||
ShellType type = processControl.getShellType();
|
||||
content = type.prepareScriptContent(content);
|
||||
|
||||
if (processControl.executeBooleanSimpleCommand(type.getFileExistsCommand(file))) {
|
||||
return file;
|
||||
}
|
||||
|
||||
TrackEvent.withTrace("proc", "Writing exec script")
|
||||
.tag("file", file)
|
||||
.tag("content", content)
|
||||
.handle();
|
||||
|
||||
processControl.executeSimpleCommand(type.getFileTouchCommand(file), "Failed to create script " + file);
|
||||
processControl.executeSimpleCommand(type.getMakeExecutableCommand(file), "Failed to make script " + file + " executable");
|
||||
|
||||
if (!content.contains("\n")) {
|
||||
processControl.executeSimpleCommand(type.getSimpleFileWriteCommand(content, file));
|
||||
return file;
|
||||
}
|
||||
|
||||
try (var c = processControl.command(type.getStreamFileWriteCommand(file)).start()) {
|
||||
c.discardOut();
|
||||
c.discardErr();
|
||||
c.getStdin().write(content.getBytes(processControl.getCharset()));
|
||||
c.closeStdin();
|
||||
}
|
||||
|
||||
if (restart) {
|
||||
processControl.restart();
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createAskPassScript(SecretValue pass, ShellProcessControl parent, ShellType type, boolean restart) {
|
||||
var content = type.getScriptEchoCommand(pass.getSecretValue());
|
||||
var temp = XPipeTempDirectory.get(parent);
|
||||
var file = FileNames.join(temp, "askpass-" + getConnectionHash(content) + "." + type.getScriptFileEnding());
|
||||
return createExecScript(parent,file, content, restart);
|
||||
}
|
||||
}
|
|
@ -3,33 +3,34 @@ package io.xpipe.extension.util;
|
|||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
import io.xpipe.extension.I18n;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Validators {
|
||||
|
||||
public static void nonNull(Object object, String name) {
|
||||
public static void nonNull(Object object, String name) throws ValidationException {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.null", name));
|
||||
throw new ValidationException(I18n.get("extension.mustNotBeEmpty", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void notEmpty(String string, String name) {
|
||||
public static void notEmpty(String string, String name) throws ValidationException {
|
||||
if (string.trim().length() == 0) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.empty", name));
|
||||
throw new ValidationException(I18n.get("extension.mustNotBeEmpty", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void namedStoreExists(DataStore store, String name) {
|
||||
public static void namedStoreExists(DataStore store, String name) throws ValidationException {
|
||||
if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.missingStore", name));
|
||||
throw new ValidationException(I18n.get("extension.missingStore", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) {
|
||||
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) throws ValidationException {
|
||||
if (!predicate.test(host)) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name));
|
||||
throw new ValidationException(I18n.get("extension.hostFeatureUnsupported", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ lf=LF (Linux)
|
|||
none=None
|
||||
common=Common
|
||||
other=Other
|
||||
nullPointer=Null Pointer: $MSG$
|
||||
nullPointer=Null Pointer
|
||||
mustNotBeEmpty=$NAME$ must not be empty
|
||||
null=$VALUE$ must be not null
|
||||
hostFeatureUnsupported=Host does not support the feature $FEATURE$
|
||||
|
|
Loading…
Reference in a new issue