More fixes for file stores

This commit is contained in:
Christopher Schnick 2022-11-23 14:15:21 +01:00
parent 54bfcd2478
commit 5e8dd42dd9
14 changed files with 165 additions and 71 deletions

View file

@ -64,7 +64,7 @@ public final class XPipeConnection extends BeaconConnection {
public static Optional<BeaconClient> waitForStartup(Process process) { public static Optional<BeaconClient> waitForStartup(Process process) {
for (int i = 0; i < 160; i++) { for (int i = 0; i < 160; i++) {
if (process != null && !process.isAlive()) { if (process != null && !process.isAlive() && process.exitValue() != 0) {
return Optional.empty(); return Optional.empty();
} }

View file

@ -38,7 +38,6 @@ public class QueryDataSourceExchange implements MessageExchange {
@NonNull @NonNull
DataSourceId id; DataSourceId id;
@NonNull
String information; String information;
@NonNull @NonNull

View file

@ -2,34 +2,48 @@ package io.xpipe.core.store;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
public interface CommandProcessControl extends ProcessControl { public interface CommandProcessControl extends ProcessControl {
default InputStream startExternalStdout() throws Exception { default InputStream startExternalStdout() throws Exception {
start(); try {
discardErr(); start();
return new FilterInputStream(getStdout()) {
@Override AtomicReference<String> err = new AtomicReference<>("");
public void close() throws IOException { accumulateStderr(s -> err.set(s));
getStdout().close();
CommandProcessControl.this.close(); return new FilterInputStream(getStdout()) {
} @Override
}; public void close() throws IOException {
CommandProcessControl.this.close();
if (!err.get().isEmpty()) {
throw new IOException(err.get());
}
}
};
} catch (Exception ex) {
close();
throw ex;
}
} }
default OutputStream startExternalStdin() throws Exception { default OutputStream startExternalStdin() throws Exception {
try (CommandProcessControl pc = start()) { try {
pc.discardOut(); start();
pc.discardErr(); discardOut();
discardErr();
return new FilterOutputStream(getStdin()) { return new FilterOutputStream(getStdin()) {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
pc.getStdin().close(); closeStdin();
pc.close(); CommandProcessControl.this.close();
} }
}; };
} catch (Exception e) { } catch (Exception ex) {
throw e; close();
throw ex;
} }
} }
@ -53,6 +67,10 @@ public interface CommandProcessControl extends ProcessControl {
readOrThrow(); readOrThrow();
} }
void accumulateStdout(Consumer<String> con);
void accumulateStderr(Consumer<String> con);
public String readOrThrow() throws Exception; public String readOrThrow() throws Exception;
public default boolean startAndCheckExit() { public default boolean startAndCheckExit() {

View file

@ -6,9 +6,11 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.regex.Pattern;
/** /**
* Represents a file located on a file system. * Represents a file located on a file system.
@ -27,6 +29,15 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
this.file = file; this.file = file;
} }
public String getParent() {
var matcher = Pattern.compile("^(.+?)[^\\\\/]+$").matcher(file);
if (!matcher.matches()) {
throw new IllegalArgumentException("Unable to determine parent of " + file);
}
return matcher.group(1);
}
public final boolean isLocal() { public final boolean isLocal() {
return fileSystem instanceof LocalStore; return fileSystem instanceof LocalStore;
} }
@ -59,6 +70,10 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override @Override
public OutputStream openOutput() throws Exception { public OutputStream openOutput() throws Exception {
if (!fileSystem.mkdirs(getParent())) {
throw new IOException("Unable to create directory: " + getParent());
}
return fileSystem.openOutput(file); return fileSystem.openOutput(file);
} }

View file

@ -25,7 +25,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
@Override @Override
public boolean mkdirs(String file) throws Exception { public boolean mkdirs(String file) throws Exception {
try { try {
Files.createDirectories(Path.of(file).getParent()); Files.createDirectories(Path.of(file));
return true; return true;
} catch (Exception ex) { } catch (Exception ex) {
return false; return false;
@ -40,7 +40,6 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
@Override @Override
public OutputStream openOutput(String file) throws Exception { public OutputStream openOutput(String file) throws Exception {
mkdirs(file);
var p = Path.of(file); var p = Path.of(file);
return Files.newOutputStream(p); return Files.newOutputStream(p);
} }

View file

@ -7,8 +7,7 @@ public interface MachineStore extends FileSystemStore, ShellStore {
@Override @Override
default void validate() throws Exception { default void validate() throws Exception {
try (ShellProcessControl pc = create().start()) { try (ShellProcessControl pc = create().start()) {}
}
} }
public default boolean isLocal() { public default boolean isLocal() {
@ -24,29 +23,29 @@ public interface MachineStore extends FileSystemStore, ShellStore {
@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(file)) return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(proc.getOsType().normalizeFileName(file)))
.startExternalStdout(); .startExternalStdout();
} }
@Override @Override
public default OutputStream openOutput(String file) throws Exception { public default OutputStream openOutput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileWriteCommand(file)) return create().commandListFunction(proc -> proc.getShellType().createFileWriteCommand(proc.getOsType().normalizeFileName(file)))
.startExternalStdin(); .startExternalStdin();
} }
@Override @Override
public default boolean exists(String file) throws Exception { public default boolean exists(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createFileExistsCommand(file)) try (var pc = create().commandListFunction(proc -> proc.getShellType().createFileExistsCommand(proc.getOsType().normalizeFileName(file)))
.start() .start()) {
.discardAndCheckExit(); return pc.discardAndCheckExit();
return r; }
} }
@Override @Override
public default boolean mkdirs(String file) throws Exception { public default boolean mkdirs(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createMkdirsCommand(file)) try (var pc = create().commandListFunction(proc -> proc.getShellType().createMkdirsCommand(proc.getOsType().normalizeFileName(file)))
.start() .start()) {
.discardAndCheckExit(); return pc.discardAndCheckExit();
return r; }
} }
} }

View file

@ -4,9 +4,19 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List;
import java.util.stream.Collectors;
public interface ProcessControl extends AutoCloseable { public interface ProcessControl extends AutoCloseable {
static String join(List<String> command) {
return command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "));
}
void closeStdin() throws IOException;
boolean isStdinClosed();
boolean isRunning(); boolean isRunning();
ShellType getShellType(); ShellType getShellType();

View file

@ -12,6 +12,16 @@ import java.util.stream.Collectors;
public interface ShellProcessControl extends ProcessControl { public interface ShellProcessControl extends ProcessControl {
default String executeSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) {
return c.readOrThrow();
}
}
default String executeSimpleCommand(ShellType type, String command) throws Exception {
return executeSimpleCommand(type.switchTo(command));
}
int getProcessId(); int getProcessId();
OsType getOsType(); OsType getOsType();

View file

@ -9,6 +9,10 @@ import java.util.List;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface ShellType { public interface ShellType {
default String joinCommands(String... s) {
return String.join(getConcatenationOperator(), s);
}
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() {
@ -29,6 +33,8 @@ public interface ShellType {
String queryShellProcessId(ShellProcessControl control) throws Exception; String queryShellProcessId(ShellProcessControl control) throws Exception;
String getSetVariableCommand(String variableName, String value);
List<String> openCommand(); List<String> openCommand();
String switchTo(String cmd); String switchTo(String cmd);

View file

@ -46,6 +46,11 @@ public class ShellTypes {
@Value @Value
public static class Cmd implements ShellType { public static class Cmd implements ShellType {
@Override
public String getSetVariableCommand(String variableName, String value) {
return "set \"" + variableName + "=" + value + "\"";
}
@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 toErrorStream ? "(echo " + s + ")1>&2" : "echo " + s;
@ -103,7 +108,7 @@ public class ShellTypes {
@Override @Override
public List<String> createMkdirsCommand(String dirs) { public List<String> createMkdirsCommand(String dirs) {
return List.of("lmkdir", dirs); return List.of("mkdir", dirs);
} }
@Override @Override
@ -113,12 +118,12 @@ public class ShellTypes {
@Override @Override
public List<String> createFileWriteCommand(String file) { public List<String> createFileWriteCommand(String file) {
return List.of("Out-File", "-FilePath", file); return List.of("findstr", "\"^\"", ">", file);
} }
@Override @Override
public List<String> createFileExistsCommand(String file) { public List<String> createFileExistsCommand(String file) {
return List.of("if", "exist", file, "echo", "hi"); return List.of("dir", "/a", file);
} }
@Override @Override
@ -158,6 +163,11 @@ public class ShellTypes {
@Value @Value
public static class PowerShell implements ShellType { public static class PowerShell implements ShellType {
@Override
public String getSetVariableCommand(String variableName, String value) {
return "set " + variableName + "=" + value;
}
@Override @Override
public String queryShellProcessId(ShellProcessControl control) throws IOException { public String queryShellProcessId(ShellProcessControl control) throws IOException {
control.writeLine("powershell (Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId"); control.writeLine("powershell (Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId");
@ -217,24 +227,24 @@ public class ShellTypes {
return "powershell.exe -Command " + cmd; return "powershell.exe -Command " + cmd;
} }
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("New-Item", "-Path", dirs, "-ItemType", "Directory");
}
@Override @Override
public List<String> createFileReadCommand(String file) { public List<String> createFileReadCommand(String file) {
return List.of("Get-Content", file); return List.of("cmd", "/c", "type", file);
} }
@Override @Override
public List<String> createFileWriteCommand(String file) { public List<String> createFileWriteCommand(String file) {
return List.of("Out-File", "-FilePath", file); return List.of("cmd", "/c", "findstr", "\"^\"", ">", file);
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("cmd", "/c", "mkdir", dirs);
} }
@Override @Override
public List<String> createFileExistsCommand(String file) { public List<String> createFileExistsCommand(String file) {
return List.of("Test-Path", "-path", file); return List.of("cmd", "/c", "dir", "/a", file);
} }
@Override @Override
@ -285,12 +295,12 @@ public class ShellTypes {
@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) {
control.executeCommand("SUDO_ASKPASS=/bin/false; sudo -p \"\" -S " + command); control.executeCommand("SUDO_ASKPASS=/bin/false sudo -p \"\" -S " + command);
return; return;
} }
control.executeCommand("sudo -p \"\" -S " + command); // For sudo to always query for a password by using the -k switch
// Thread.sleep(200); control.executeCommand("sudo -p \"\" -k -S " + command);
control.writeLine(control.getElevationPassword().getSecretValue()); control.writeLine(control.getElevationPassword().getSecretValue());
} }
@ -314,6 +324,12 @@ public class ShellTypes {
return matcher.group(0); return matcher.group(0);
} }
} }
@Override
public String getSetVariableCommand(String variableName, String value) {
return variableName + "=" + value;
}
@Override @Override
public List<String> openCommand() { public List<String> openCommand() {
return List.of("sh", "-i", "-l"); return List.of("sh", "-i", "-l");
@ -336,7 +352,7 @@ public class ShellTypes {
@Override @Override
public List<String> createFileWriteCommand(String file) { public List<String> createFileWriteCommand(String file) {
return List.of(file); return List.of("cat", ">", file);
} }
@Override @Override

View file

@ -18,6 +18,8 @@ public interface OsType {
String getName(); String getName();
String normalizeFileName(String file);
Map<String, String> getProperties(ShellProcessControl pc) throws Exception; Map<String, String> getProperties(ShellProcessControl pc) throws Exception;
String determineOperatingSystemName(ShellProcessControl pc) throws Exception; String determineOperatingSystemName(ShellProcessControl pc) throws Exception;
@ -46,6 +48,11 @@ public interface OsType {
return "Windows"; return "Windows";
} }
@Override
public String normalizeFileName(String file) {
return String.join("\\", file.split("[\\\\/]+"));
}
@Override @Override
public Map<String, String> getProperties(ShellProcessControl pc) throws Exception { public Map<String, String> getProperties(ShellProcessControl pc) throws Exception {
try (CommandProcessControl c = try (CommandProcessControl c =
@ -79,6 +86,11 @@ public interface OsType {
static class Linux implements OsType { static class Linux implements OsType {
@Override
public String normalizeFileName(String file) {
return String.join("/", file.split("[\\\\/]+"));
}
@Override @Override
public String getName() { public String getName() {
return "Linux"; return "Linux";
@ -139,6 +151,11 @@ public interface OsType {
static class Mac implements OsType { static class Mac implements OsType {
@Override
public String normalizeFileName(String file) {
return String.join("/", file.split("[\\\\/]+"));
}
@Override @Override
public String getName() { public String getName() {
return "Mac"; return "Mac";

View file

@ -1,13 +1,31 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import io.xpipe.api.DataSource;
import io.xpipe.api.util.XPipeDaemonController; import io.xpipe.api.util.XPipeDaemonController;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.XPipeServiceProviders;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
public class DaemonExtensionTest { public class DaemonExtensionTest extends ExtensionTest {
public static DataSource getSource(String type, DataStore store) {
return DataSource.create(null, type, store);
}
public static DataSource getSource(String type, String file) {
return DataSource.create(null, type, getResource(file));
}
public static DataSource getSource(io.xpipe.core.source.DataSource<?> source) {
return DataSource.create(null, source);
}
@BeforeAll @BeforeAll
public static void setup() throws Exception { public static void setup() throws Exception {
JacksonMapper.initModularized(ModuleLayer.boot());
XPipeServiceProviders.load(ModuleLayer.boot());
XPipeDaemonController.start(); XPipeDaemonController.start();
} }

View file

@ -1,18 +1,13 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import io.xpipe.api.DataSource;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileStore; import io.xpipe.core.store.FileStore;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.XPipeServiceProviders;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeAll;
import java.nio.file.Path; import java.nio.file.Path;
public class ExtensionTest { public class ExtensionTest {
@SneakyThrows @SneakyThrows
public static DataStore getResource(String name) { public static DataStore getResource(String name) {
var url = DaemonExtensionTest.class.getClassLoader().getResource(name); var url = DaemonExtensionTest.class.getClassLoader().getResource(name);
@ -22,22 +17,4 @@ public class ExtensionTest {
var file = Path.of(url.toURI()).toString(); var file = Path.of(url.toURI()).toString();
return FileStore.local(Path.of(file)); return FileStore.local(Path.of(file));
} }
public static DataSource getSource(String type, DataStore store) {
return DataSource.create(null, type, store);
}
public static DataSource getSource(String type, String file) {
return DataSource.create(null, type, getResource(file));
}
public static DataSource getSource(io.xpipe.core.source.DataSource<?> source) {
return DataSource.create(null, source);
}
@BeforeAll
public static void setup() throws Exception {
JacksonMapper.initModularized(ModuleLayer.boot());
XPipeServiceProviders.load(ModuleLayer.boot());
}
} }

View file

@ -1,4 +1,14 @@
package io.xpipe.extension.util; package io.xpipe.extension.util;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.XPipeServiceProviders;
import org.junit.jupiter.api.BeforeAll;
public class LocalExtensionTest extends ExtensionTest { public class LocalExtensionTest extends ExtensionTest {
@BeforeAll
public static void setup() throws Exception {
JacksonMapper.initModularized(ModuleLayer.boot());
XPipeServiceProviders.load(ModuleLayer.boot());
}
} }