Improvements and refactor for shell connections

This commit is contained in:
crschnick 2023-03-02 16:57:49 +00:00
parent d5a7e2fb64
commit d879c13aa4
38 changed files with 700 additions and 191 deletions

View file

@ -132,7 +132,7 @@ application {
run {
systemProperty 'io.xpipe.app.mode', 'gui'
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local_stage/"
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local7/"
systemProperty 'io.xpipe.app.writeLogs', "true"
systemProperty 'io.xpipe.app.writeSysOut', "true"
systemProperty 'io.xpipe.app.developerMode', "true"

View file

@ -7,7 +7,7 @@ import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.util.*;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem;
import javafx.beans.property.Property;
import javafx.scene.control.ContextMenu;
@ -73,7 +73,7 @@ final class FileContextMenu extends ContextMenu {
var execute = new MenuItem("Run in terminal");
execute.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
ShellProcessControl pc =
ShellControl pc =
model.getFileSystem().getShell().orElseThrow();
pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath()));
var cmd = pc.command("\"" + entry.getPath() + "\"").prepareTerminalOpen();
@ -86,7 +86,7 @@ final class FileContextMenu extends ContextMenu {
var executeInBackground = new MenuItem("Run in background");
executeInBackground.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
ShellProcessControl pc =
ShellControl pc =
model.getFileSystem().getShell().orElseThrow();
pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath()));
var cmd = ScriptHelper.createDetachCommand(pc, "\"" + entry.getPath() + "\"");

View file

@ -178,9 +178,9 @@ final class OpenFileSystemModel {
var current = !(fileSystem instanceof LocalStore) && fs instanceof ConnectionFileSystem connectionFileSystem
? connectionFileSystem
.getShellProcessControl()
.getShellControl()
.executeStringSimpleCommand(connectionFileSystem
.getShellProcessControl()
.getShellControl()
.getShellDialect()
.getPrintWorkingDirectoryCommand())
: null;
@ -199,7 +199,7 @@ final class OpenFileSystemModel {
ThreadHelper.runFailableAsync(() -> {
BusyProperty.execute(busy, () -> {
if (store.getValue() instanceof ShellStore s) {
var connection = ((ConnectionFileSystem) fileSystem).getShellProcessControl();
var connection = ((ConnectionFileSystem) fileSystem).getShellControl();
var command = s.create()
.initWith(List.of(connection.getShellDialect().getCdCommand(directory)))
.prepareTerminalOpen();

View file

@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -16,7 +17,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
private final ObservableList<T> shown;
private final ObservableList<T> all;
@ -29,7 +30,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
}
@Override
public CompStructure<VBox> createBase() {
public CompStructure<ScrollPane> createBase() {
Map<T, Region> cache = new HashMap<>();
VBox listView = new VBox();
@ -46,7 +47,11 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
cache.keySet().retainAll(c.getList());
});
return new SimpleCompStructure<>(listView);
var scroll = new ScrollPane(listView);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToWidth(true);
return new SimpleCompStructure<>(scroll);
}
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {

View file

@ -24,6 +24,7 @@ public class ListSelectorComp<T> extends SimpleComp {
@Override
protected Region createSimple() {
var vbox = new VBox();
vbox.setSpacing(8);
vbox.getStyleClass().add("content");
for (var v : values) {
var cb = new CheckBox(null);

View file

@ -25,7 +25,6 @@ import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Alert;
import javafx.scene.control.Separator;
import javafx.scene.layout.BorderPane;
@ -160,10 +159,11 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
}
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
return new DynamicOptionsBuilder(false)
.addComp((ObservableValue<String>) null, comp, input)
.addTitle(AppI18n.observable("properties"))
.addString(AppI18n.observable("name"), name, false)
return new OptionsBuilder()
.addComp(comp, input)
.name("connectionName")
.description("connectionNameDescription")
.addString(name, false)
.nonNull(propVal)
.bind(
() -> {
@ -218,6 +218,11 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
return new LoadingOverlayComp(Comp.of(() -> layout), busy).createStructure();
}
@Override
public void onContinue() {
ScanAlert.showIfNeeded(entry.getValue().getStore());
}
@Override
public boolean canContinue() {
if (provider.getValue() != null) {

View file

@ -66,7 +66,7 @@ public class AppGreetings {
}
public static void showIfNeeded() {
//TODO
// TODO
if (!AppProperties.get().isImage() || true) {
return;
}
@ -113,6 +113,7 @@ public class AppGreetings {
var button = alert.getDialogPane().lookupButton(buttonType);
button.disableProperty().bind(accepted.not());
},
null,
r -> r.filter(b -> b.getButtonData().isDefaultButton() && accepted.get())
.ifPresentOrElse(
t -> {

View file

@ -7,6 +7,7 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.SupportedLocale;
import io.xpipe.app.util.DynamicOptionsBuilder;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.Translatable;
import io.xpipe.core.util.ModuleHelper;
import javafx.beans.binding.Bindings;
@ -131,6 +132,7 @@ private static AppI18n INSTANCE = new AppI18n();
|| caller.equals(FancyTooltipAugment.class)
|| caller.equals(PrefsChoiceValue.class)
|| caller.equals(Translatable.class)
|| caller.equals(OptionsBuilder.class)
|| caller.equals(DynamicOptionsBuilder.class)) {
continue;
}

View file

@ -72,7 +72,7 @@ public class AppWindowHelper {
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2);
}
public static void showAlert(Consumer<Alert> c, Consumer<Optional<ButtonType>> bt) {
public static void showAlert(Consumer<Alert> c, ObservableValue<Boolean> loading, Consumer<Optional<ButtonType>> bt) {
ThreadHelper.runAsync(() -> {
var r = showBlockingAlert(c);
if (bt != null) {

View file

@ -0,0 +1,49 @@
package io.xpipe.app.ext;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.ModuleLayerLoader;
import lombok.Value;
import org.apache.commons.lang3.function.FailableRunnable;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public abstract class ScanProvider {
@Value
public static class ScanOperation {
String nameKey;
FailableRunnable<Exception> scanner;
}
private static List<ScanProvider> ALL;
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
ALL = ServiceLoader.load(layer, ScanProvider.class).stream()
.map(ServiceLoader.Provider::get)
.sorted(Comparator.comparing(scanProvider -> scanProvider.getClass().getName()))
.collect(Collectors.toList());
}
@Override
public boolean requiresFullDaemon() {
return true;
}
@Override
public boolean prioritizeLoading() {
return false;
}
}
public static List<ScanProvider> getAll() {
return ALL;
}
public abstract ScanOperation create(DataStore store);
}

View file

@ -0,0 +1,163 @@
package io.xpipe.app.fxcomps.impl;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import java.util.ArrayList;
import java.util.List;
public class OptionsComp extends Comp<CompStructure<Pane>> {
private final List<OptionsComp.Entry> entries;
public OptionsComp(List<OptionsComp.Entry> entries) {
this.entries = entries;
}
public OptionsComp.Entry queryEntry(String key) {
return entries.stream()
.filter(entry -> entry.key != null && entry.key.equals(key))
.findAny()
.orElseThrow();
}
@Override
public CompStructure<Pane> createBase() {
Pane pane;
var content = new VBox();
content.setSpacing(7);
pane = content;
pane.getStyleClass().add("options-comp");
var nameRegions = new ArrayList<Region>();
var compRegions = new ArrayList<Region>();
for (var entry : getEntries()) {
Region compRegion = null;
if (entry.comp() != null) {
compRegion = entry.comp().createRegion();
}
if (entry.name() != null && entry.description() != null) {
var line = new VBox();
line.prefWidthProperty().bind(pane.widthProperty());
line.setSpacing(5);
var name = new Label();
name.getStyleClass().add("name");
name.textProperty().bind(entry.name());
name.setMinWidth(Region.USE_PREF_SIZE);
name.setAlignment(Pos.CENTER_LEFT);
if (compRegion != null) {
name.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
name.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
}
line.getChildren().add(name);
var description = new Label();
description.setWrapText(true);
description.getStyleClass().add("description");
description.textProperty().bind(entry.description());
description.setAlignment(Pos.CENTER_LEFT);
if (compRegion != null) {
description.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
description.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
}
if (entry.longDescription() != null) {
var popover = new Popover(new Label(entry.longDescription().getValue()));
popover.setCloseButtonEnabled(false);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
AppFont.small(popover.getContentNode());
var descriptionHover = new Label("?");
AppFont.header(descriptionHover);
descriptionHover.setOnMouseClicked(e -> popover.show(descriptionHover));
var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), descriptionHover);
HBox.setHgrow(descriptionBox, Priority.ALWAYS);
descriptionBox.setAlignment(Pos.CENTER_LEFT);
line.getChildren().add(descriptionBox);
}else {
line.getChildren().add(description);
}
if (compRegion != null) {
line.getChildren().add(compRegion);
}
pane.getChildren().add(line);
}
else if (entry.name() != null) {
var line = new HBox();
line.setFillHeight(true);
line.prefWidthProperty().bind(pane.widthProperty());
line.setSpacing(8);
var name = new Label();
name.textProperty().bind(entry.name());
name.prefHeightProperty().bind(line.heightProperty());
name.setMinWidth(Region.USE_PREF_SIZE);
name.setAlignment(Pos.CENTER_LEFT);
if (compRegion != null) {
name.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
name.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
}
nameRegions.add(name);
line.getChildren().add(name);
if (compRegion != null) {
compRegions.add(compRegion);
line.getChildren().add(compRegion);
HBox.setHgrow(compRegion, Priority.ALWAYS);
}
pane.getChildren().add(line);
} else {
if (compRegion != null) {
compRegions.add(compRegion);
pane.getChildren().add(compRegion);
}
}
}
if (entries.stream().anyMatch(entry -> entry.name() != null && entry.description() == null)) {
var nameWidthBinding = Bindings.createDoubleBinding(
() -> {
if (nameRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
return Region.USE_COMPUTED_SIZE;
}
var m = nameRegions.stream()
.map(Region::getWidth)
.max(Double::compareTo)
.orElse(0.0);
return m;
},
nameRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding));
}
return new SimpleCompStructure<>(pane);
}
public List<OptionsComp.Entry> getEntries() {
return entries;
}
public record Entry(String key, ObservableValue<String> description, ObservableValue<String> longDescription, ObservableValue<String> name, Comp<?> comp) {}
}

View file

@ -4,7 +4,7 @@ import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.nio.file.Files;
import java.nio.file.Path;
@ -37,7 +37,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
protected Optional<Path> getApplicationPath() {
try (ShellProcessControl pc = LocalStore.getShell().start()) {
try (ShellControl pc = LocalStore.getShell().start()) {
try (var c = pc.command(String.format(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister "
+ "-dump | grep -o \"/.*%s.app\" | grep -v -E \"Caches|TimeMachine|Temporary|/Volumes/%s\" | uniq",
@ -76,7 +76,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
public boolean isAvailable() {
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();

View file

@ -6,7 +6,7 @@ import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.MacOsPermissions;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.util.List;
@ -133,7 +133,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
var suffix = command.equals(pc.getShellDialect().getNormalOpenCommand())
? "\"\""
: "\"" + command.replaceAll("\"", "\\\\\"") + "\"";
@ -187,7 +187,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
var cmd = String.format(
"""
osascript - "$@" <<EOF
@ -225,7 +225,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return;
}
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
var cmd = String.format(
"""
osascript - "$@" <<EOF
@ -258,7 +258,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
ApplicationHelper.checkSupport(pc, executable, displayName);
var toExecute = executable + " " + toCommand(name, command);
@ -274,7 +274,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
protected abstract String toCommand(String name, String command);
public boolean isAvailable() {
try (ShellProcessControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalStore.getShell()) {
return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();

View file

@ -53,6 +53,8 @@ public abstract class DataStorage {
INSTANCE = shouldPersist() ? new StandardStorage() : new ImpersistentStorage();
INSTANCE.load();
INSTANCE.storeEntries.forEach(entry -> entry.simpleRefresh());
DataStoreProviders.getAll().forEach(dataStoreProvider -> {
try {
dataStoreProvider.storageInit();
@ -115,6 +117,21 @@ public abstract class DataStorage {
return createUniqueSourceEntryName(col, typeName);
}
private String createUniqueStoreEntryName(String base) {
if (DataStorage.get().getStoreIfPresent(base).isEmpty()) {
return base;
}
int counter = 1;
while (true) {
var name = base + counter;
if (DataStorage.get().getStoreIfPresent(name).isEmpty()) {
return name;
}
counter++;
}
}
private String createUniqueSourceEntryName(DataSourceCollection col, String base) {
base = DataSourceId.cleanString(base);
var id = DataSourceId.create(col != null ? col.getName() : null, base);
@ -345,7 +362,7 @@ public abstract class DataStorage {
}
public DataStoreEntry addStore(@NonNull String name, DataStore store) {
var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
addStore(e);
return e;
}

View file

@ -6,9 +6,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.util.ScriptHelper;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.XPipeInstallation;
@ -22,7 +22,7 @@ import java.util.List;
public class AppInstaller {
public static void installOnRemoteMachine(ShellProcessControl s, String version) throws Exception {
public static void installOnRemoteMachine(ShellControl s, String version) throws Exception {
var asset = getSuitablePlatformAsset(s);
var file = AppDownloads.downloadInstaller(asset, version);
installFile(s, asset, file);
@ -32,14 +32,14 @@ public class AppInstaller {
asset.installLocal(localFile.toString());
}
public static void installFile(ShellProcessControl s, InstallerAssetType asset, Path localFile) throws Exception {
public static void installFile(ShellControl s, InstallerAssetType asset, Path localFile) throws Exception {
String targetFile = null;
if (s.isLocal()) {
targetFile = localFile.toString();
} else {
targetFile = FileNames.join(
s.getTemporaryDirectory(), localFile.getFileName().toString());
try (CommandProcessControl c = s.getShellDialect().getStreamFileWriteCommand(s, targetFile)
try (CommandControl c = s.getShellDialect().getStreamFileWriteCommand(s, targetFile)
.start()) {
c.discardOut();
c.discardErr();
@ -73,13 +73,13 @@ public class AppInstaller {
throw new AssertionError();
}
public static InstallerAssetType getSuitablePlatformAsset(ShellProcessControl p) throws Exception {
public static InstallerAssetType getSuitablePlatformAsset(ShellControl p) throws Exception {
if (p.getOsType().equals(OsType.WINDOWS)) {
return new InstallerAssetType.Msi();
}
if (p.getOsType().equals(OsType.LINUX)) {
try (CommandProcessControl c = p.command(p.getShellDialect().getFileExistsCommand("/etc/debian_version"))
try (CommandControl c = p.command(p.getShellDialect().getFileExistsCommand("/etc/debian_version"))
.start()) {
return c.discardAndCheckExit() ? new InstallerAssetType.Debian() : new InstallerAssetType.Rpm();
}
@ -102,7 +102,7 @@ public class AppInstaller {
})
public abstract static class InstallerAssetType {
public abstract void installRemote(ShellProcessControl pc, String file) throws Exception;
public abstract void installRemote(ShellControl pc, String file) throws Exception;
public abstract void installLocal(String file) throws Exception;
@ -121,11 +121,11 @@ public class AppInstaller {
}
@Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception {
public void installRemote(ShellControl shellControl, String file) throws Exception {
var exec = XPipeInstallation.getInstallationExecutable(
shellProcessControl,
XPipeInstallation.getDefaultInstallationBasePath(shellProcessControl, false));
var logsDir = FileNames.join(XPipeInstallation.getDataBasePath(shellProcessControl), "logs");
shellControl,
XPipeInstallation.getDefaultInstallationBasePath(shellControl, false));
var logsDir = FileNames.join(XPipeInstallation.getDataBasePath(shellControl), "logs");
var cmd = new ArrayList<>(java.util.List.of(
"start",
"/wait",
@ -139,7 +139,7 @@ public class AppInstaller {
exec
// "/qf"
));
try (CommandProcessControl c = shellProcessControl.command(cmd).start()) {
try (CommandControl c = shellControl.command(cmd).start()) {
c.discardOrThrow();
}
}
@ -177,14 +177,14 @@ public class AppInstaller {
}
@Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command("DEBIAN_FRONTEND=noninteractive apt-get remove -qy xpipe")
public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandControl c = pc.command("DEBIAN_FRONTEND=noninteractive apt-get remove -qy xpipe")
.elevated()
.start()) {
c.discardOrThrow();
}
try (CommandProcessControl c = pc.command(
try (CommandControl c = pc.command(
"DEBIAN_FRONTEND=noninteractive apt-get install -qy \"" + file + "\"")
.elevated()
.start()) {
@ -214,9 +214,9 @@ public class AppInstaller {
}
@Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command("rpm -U -v --force \"" + file + "\"")
public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandControl c = pc.command("rpm -U -v --force \"" + file + "\"")
.elevated()
.start()) {
c.discardOrThrow();
@ -244,9 +244,9 @@ public class AppInstaller {
}
@Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command(
public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandControl c = pc.command(
"installer -verboseR -allowUntrusted -pkg \"" + file + "\" -target /")
.elevated()
.start()) {

View file

@ -38,7 +38,7 @@ public class UpdateChangelogAlert {
alert.getDialogPane().setContent(markdown);
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
},
},null,
r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {}));
}
}

View file

@ -3,7 +3,7 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.io.IOException;
import java.util.List;
@ -30,12 +30,12 @@ public class ApplicationHelper {
}
}
public static boolean isInPath(ShellProcessControl processControl, String executable) throws Exception {
public static boolean isInPath(ShellControl processControl, String executable) throws Exception {
return processControl.executeBooleanSimpleCommand(
processControl.getShellDialect().getWhichCommand(executable));
}
public static void checkSupport(ShellProcessControl processControl, String executable, String displayName)
public static void checkSupport(ShellControl processControl, String executable, String displayName)
throws Exception {
if (!isInPath(processControl, executable)) {
throw new IOException(displayName + " executable " + executable + " not found in PATH");

View file

@ -38,7 +38,7 @@ public class MacOsPermissions {
a.getDialogPane().setContent(AppWindowHelper.alertContentText(AppI18n.get("permissionsAlertTitleContent")));
a.getButtonTypes().clear();
alert.set(a);
}, buttonType -> {
}, null, buttonType -> {
alert.get().close();
if (buttonType.isEmpty() || !buttonType.get().getButtonData().isDefaultButton()) {
state.set(false);

View file

@ -0,0 +1,166 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.core.util.SecretValue;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import net.synedra.validatorfx.Check;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class OptionsBuilder {
private final List<OptionsComp.Entry> entries = new ArrayList<>();
private final List<Property<?>> props = new ArrayList<>();
private ObservableValue<String> name;
private ObservableValue<String> description;
private ObservableValue<String> longDescription;
private Comp<?> comp;
private void finishCurrent() {
if (comp == null) {
return;
}
var entry = new OptionsComp.Entry(null, description, longDescription, name, comp);
description = null;
longDescription = null;
name = null;
comp = null;
entries.add(entry);
}
public OptionsBuilder addTitle(String titleKey) {
finishCurrent();
entries.add(new OptionsComp.Entry(
titleKey, null, null, null, new LabelComp(AppI18n.observable(titleKey)).styleClass("title-header")));
return this;
}
public OptionsBuilder addTitle(ObservableValue<String> title) {
finishCurrent();
entries.add(new OptionsComp.Entry(
null, null, null, null, Comp.of(() -> new Label(title.getValue())).styleClass("title-header")));
return this;
}
public OptionsBuilder decorate(Check c) {
comp.apply(s -> c.decorates(s.get()));
return this;
}
public OptionsBuilder nonNull(Validator v) {
var e = name;
var p = props.get(props.size() - 1);
return decorate(Validator.nonNull(v, e, p));
}
private void pushComp(Comp<?> comp) {
finishCurrent();
this.comp = comp;
}
public OptionsBuilder stringArea(Property<String> prop, boolean lazy) {
var comp = new TextAreaComp(prop, lazy);
pushComp(comp);
props.add(prop);
return this;
}
public OptionsBuilder addInteger(Property<Integer> prop) {
var comp = new IntFieldComp(prop);
pushComp(comp);
props.add(prop);
return this;
}
public OptionsBuilder addString(Property<String> prop) {
return addString(prop, false);
}
public OptionsBuilder addString(Property<String> prop, boolean lazy) {
var comp = new TextFieldComp(prop, lazy);
pushComp(comp);
props.add(prop);
return this;
}
public OptionsBuilder name(String nameKey) {
finishCurrent();
name = AppI18n.observable(nameKey);
return this;
}
public OptionsBuilder description(String descriptionKey) {
finishCurrent();
description = AppI18n.observable(descriptionKey);
return this;
}
public OptionsBuilder longDescription(String descriptionKey) {
finishCurrent();
longDescription = AppI18n.observable(descriptionKey);
return this;
}
public OptionsBuilder addComp(Comp<?> comp) {
pushComp(comp);
return this;
}
public OptionsBuilder addComp(Comp<?> comp, Property<?> prop) {
pushComp(comp);
props.add(prop);
return this;
}
public OptionsBuilder addSecret(Property<SecretValue> prop) {
var comp = new SecretFieldComp(prop);
pushComp(comp);
props.add(prop);
return this;
}
@SafeVarargs
public final <T, V extends T> OptionsBuilder bind(Supplier<V> creator, Property<T>... toSet) {
props.forEach(prop -> {
prop.addListener((c, o, n) -> {
for (Property<T> p : toSet) {
p.setValue(creator.get());
}
});
});
for (Property<T> p : toSet) {
p.setValue(creator.get());
}
return this;
}
public final <T, V extends T> OptionsBuilder bindChoice(
Supplier<Property<? extends V>> creator, Property<T> toSet) {
props.forEach(prop -> {
prop.addListener((c, o, n) -> {
toSet.unbind();
toSet.bind(creator.get());
});
});
toSet.bind(creator.get());
return this;
}
public OptionsComp buildComp() {
finishCurrent();
return new OptionsComp(entries);
}
public Region build() {
return buildComp().createRegion();
}
}

View file

@ -7,7 +7,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.update.AppDownloads;
import io.xpipe.app.update.AppInstaller;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.ProxyManagerProvider;
import io.xpipe.core.util.XPipeInstallation;
@ -31,7 +31,7 @@ public class ProxyManagerProviderImpl extends ProxyManagerProvider {
}
@Override
public Optional<String> checkCompatibility(ShellProcessControl s) throws Exception {
public Optional<String> checkCompatibility(ShellControl s) throws Exception {
var version = ModuleHelper.isImage() ? AppProperties.get().getVersion() : AppDownloads.getLatestVersion();
if (AppPrefs.get().developerDisableConnectorInstallationVersionCheck().get()) {
@ -54,7 +54,7 @@ public class ProxyManagerProviderImpl extends ProxyManagerProvider {
}
@Override
public boolean setup(ShellProcessControl s) throws Exception {
public boolean setup(ShellControl s) throws Exception {
var message = checkCompatibility(s);
if (message.isPresent()) {
if (showAlert()) {

View file

@ -0,0 +1,64 @@
package io.xpipe.app.util;
import io.xpipe.app.comp.base.ListSelectorComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.ScanProvider;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import java.util.ArrayList;
import java.util.List;
public class ScanAlert {
public static void showIfNeeded(DataStore store) {
var providers = ScanProvider.getAll();
var applicable = providers.stream()
.map(scanProvider -> scanProvider.create(store))
.filter(scanOperation -> scanOperation != null)
.toList();
if (applicable.size() == 0) {
return;
}
var selected = new SimpleListProperty<ScanProvider.ScanOperation>(
FXCollections.observableList(new ArrayList<>(applicable)));
var busy = new SimpleBooleanProperty();
AppWindowHelper.showAlert(
alert -> {
alert.setAlertType(Alert.AlertType.NONE);
alert.setTitle(AppI18n.get("scanAlertTitle"));
alert.setWidth(300);
var content = new VerticalComp(List.of(
new LabelComp(AppI18n.get("scanAlertHeader")).apply(struc -> struc.get().setWrapText(true)),
new ListSelectorComp<>(
applicable, scanOperation -> AppI18n.get(scanOperation.getNameKey()), selected)))
.apply(struc -> struc.get().setSpacing(15))
.styleClass("window-content")
.createRegion();
alert.getButtonTypes().add(ButtonType.OK);
alert.getDialogPane().setContent(content);
},
busy,
buttonType -> {
if (buttonType.isPresent()
&& buttonType.get().getButtonData().isDefaultButton()) {
try (var ignored = new BusyProperty(busy)) {
for (var a : applicable) {
a.getScanner().run();
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
});
}
}

View file

@ -6,7 +6,7 @@ import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.SecretValue;
import lombok.SneakyThrows;
@ -15,7 +15,7 @@ import java.util.Random;
public class ScriptHelper {
public static String createDefaultOpenCommand(ShellProcessControl pc, String file) {
public static String createDefaultOpenCommand(ShellControl pc, String file) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
return "\"" + file + "\"";
} else if (pc.getOsType().equals(OsType.LINUX)){
@ -25,7 +25,7 @@ public class ScriptHelper {
}
}
public static String createDetachCommand(ShellProcessControl pc, String command) {
public static String createDetachCommand(ShellControl pc, String command) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
return "start \"\" " + command;
} else {
@ -123,7 +123,7 @@ public class ScriptHelper {
}
public static String constructOpenWithInitScriptCommand(
ShellProcessControl processControl, List<String> init, String toExecuteInShell) {
ShellControl processControl, List<String> init, String toExecuteInShell) {
ShellDialect t = processControl.getShellDialect();
if (init.size() == 0 && toExecuteInShell == null) {
return t.getNormalOpenCommand();
@ -168,12 +168,12 @@ public class ScriptHelper {
}
@SneakyThrows
public static String getExecScriptFile(ShellProcessControl processControl) {
public static String getExecScriptFile(ShellControl processControl) {
return getExecScriptFile(processControl, processControl.getShellDialect().getScriptFileEnding());
}
@SneakyThrows
public static String getExecScriptFile(ShellProcessControl processControl, String fileEnding) {
public static String getExecScriptFile(ShellControl processControl, String fileEnding) {
var fileName = "exec-" + getScriptId();
var temp = processControl.getTemporaryDirectory();
var file = FileNames.join(temp, fileName + "." + fileEnding);
@ -181,7 +181,7 @@ public class ScriptHelper {
}
@SneakyThrows
public static String createExecScript(ShellProcessControl processControl, String content) {
public static String createExecScript(ShellControl processControl, String content) {
var fileName = "exec-" + getScriptId();
ShellDialect type = processControl.getShellDialect();
var temp = processControl.getTemporaryDirectory();
@ -190,7 +190,7 @@ public class ScriptHelper {
}
@SneakyThrows
private static String createExecScript(ShellProcessControl processControl, String file, String content) {
private static String createExecScript(ShellControl processControl, String file, String content) {
ShellDialect type = processControl.getShellDialect();
content = type.prepareScriptContent(content);
@ -207,7 +207,7 @@ public class ScriptHelper {
}
@SneakyThrows
public static String createAskPassScript(SecretValue pass, ShellProcessControl parent, ShellDialect type) {
public static String createAskPassScript(SecretValue pass, ShellControl parent, ShellDialect type) {
var content = type.getScriptEchoCommand(pass.getSecretValue());
var temp = parent.getTemporaryDirectory();
var file = FileNames.join(temp, "askpass-" + getScriptId() + "." + type.getScriptFileEnding());

View file

@ -117,11 +117,13 @@ open module io.xpipe.app {
uses XPipeDaemon;
uses ProxyFunction;
uses ModuleLayerLoader;
uses ScanProvider;
provides ModuleLayerLoader with
DataSourceTarget.Loader,
ActionProvider.Loader,
PrefsProvider.Loader;
PrefsProvider.Loader,
ScanProvider.Loader;
provides DataStateProvider with
DataStateProviderImpl;
provides ProxyManagerProvider with

View file

@ -10,7 +10,11 @@ mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$
missingStore=$NAME$ does not exist
connectionName=Connection name
connectionNameDescription=Give this connection a custom name
unknown=Unknown
scanAlertTitle=Connection detection
scanAlertHeader=Select types of connections you want to automatically detect on the host system:
namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available

View file

@ -10,4 +10,13 @@
.choice-pane-comp {
-fx-spacing: 7px;
}
.options-comp .name {
-fx-padding: 9px 0 0 0;
-fx-font-size: 1.1em;
}
.options-comp .description {
-fx-opacity: 0.75;
}

View file

@ -3,7 +3,7 @@ package io.xpipe.core.impl;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.FileSystemStore;
@ -15,12 +15,12 @@ import java.nio.file.Path;
@JsonTypeName("local")
public class LocalStore extends JacksonizedValue implements ShellStore {
private static ShellProcessControl local;
private static ShellControl local;
private static FileSystem localFileSystem;
public static ShellProcessControl getShell() throws Exception {
public static ShellControl getShell() throws Exception {
if (local == null) {
local = new LocalStore().create().start();
local = ProcessControlProvider.createLocal(false).start();
}
return local;
@ -128,7 +128,7 @@ public class LocalStore extends JacksonizedValue implements ShellStore {
}
@Override
public ShellProcessControl createControl() {
return ProcessControlProvider.createLocal();
public ShellControl createControl() {
return ProcessControlProvider.createLocal(true);
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.core.process;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.util.FailableFunction;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -8,18 +9,18 @@ import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.function.Consumer;
public interface CommandProcessControl extends ProcessControl {
public interface CommandControl extends ProcessControl {
public CommandProcessControl doesNotObeyReturnValueConvention();
public CommandControl doesNotObeyReturnValueConvention();
@Override
public CommandProcessControl sensitive();
public CommandControl sensitive();
CommandProcessControl complex();
CommandControl complex();
CommandProcessControl workingDirectory(String directory);
CommandControl workingDirectory(String directory);
ShellProcessControl getParent();
ShellControl getParent();
InputStream startExternalStdout() throws Exception;
@ -27,16 +28,20 @@ public interface CommandProcessControl extends ProcessControl {
public boolean waitFor();
CommandProcessControl customCharset(Charset charset);
CommandControl customCharset(Charset charset);
int getExitCode();
CommandProcessControl elevated();
default CommandControl elevated() {
return elevated((v) -> true);
}
CommandControl elevated(FailableFunction<ShellControl, Boolean, Exception> elevationFunction);
@Override
CommandProcessControl start() throws Exception;
CommandControl start() throws Exception;
CommandProcessControl exitTimeout(Integer timeout);
CommandControl exitTimeout(Integer timeout);
public void withStdoutOrThrow(Charsetter.FailableConsumer<InputStreamReader, Exception> c) throws Exception;
String readOnlyStdout() throws Exception;

View file

@ -22,25 +22,32 @@ public interface OsType {
}
}
String getHomeDirectory(ShellControl pc) throws Exception;
String getName();
String getTempDirectory(ShellProcessControl pc) throws Exception;
String getTempDirectory(ShellControl pc) throws Exception;
String normalizeFileName(String file);
Map<String, String> getProperties(ShellProcessControl pc) throws Exception;
Map<String, String> getProperties(ShellControl pc) throws Exception;
String determineOperatingSystemName(ShellProcessControl pc) throws Exception;
String determineOperatingSystemName(ShellControl pc) throws Exception;
static class Windows implements OsType {
@Override
public String getHomeDirectory(ShellControl pc) throws Exception {
return pc.executeStringSimpleCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("USERPROFILE"));
}
@Override
public String getName() {
return "Windows";
}
@Override
public String getTempDirectory(ShellProcessControl pc) throws Exception {
public String getTempDirectory(ShellControl pc) throws Exception {
return pc.executeStringSimpleCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("TEMP"));
}
@ -50,15 +57,15 @@ public interface OsType {
}
@Override
public Map<String, String> getProperties(ShellProcessControl pc) throws Exception {
try (CommandProcessControl c = pc.command("systeminfo").start()) {
public Map<String, String> getProperties(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("systeminfo").start()) {
var text = c.readOrThrow();
return PropertiesFormatsParser.parse(text, ":");
}
}
@Override
public String determineOperatingSystemName(ShellProcessControl pc) throws Exception {
public String determineOperatingSystemName(ShellControl pc) throws Exception {
var properties = getProperties(pc);
return properties.get("OS Name") + " "
+ properties.get("OS Version").split(" ")[0];
@ -68,7 +75,12 @@ public interface OsType {
static class Linux implements OsType {
@Override
public String getTempDirectory(ShellProcessControl pc) throws Exception {
public String getHomeDirectory(ShellControl pc) throws Exception {
return pc.executeStringSimpleCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("HOME"));
}
@Override
public String getTempDirectory(ShellControl pc) throws Exception {
return "/tmp/";
}
@ -83,20 +95,20 @@ public interface OsType {
}
@Override
public Map<String, String> getProperties(ShellProcessControl pc) throws Exception {
public Map<String, String> getProperties(ShellControl pc) throws Exception {
return null;
}
@Override
public String determineOperatingSystemName(ShellProcessControl pc) throws Exception {
try (CommandProcessControl c = pc.command("lsb_release -a").start()) {
public String determineOperatingSystemName(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("lsb_release -a").start()) {
var text = c.readOnlyStdout();
if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", null);
}
}
try (CommandProcessControl c = pc.command("cat /etc/*release").start()) {
try (CommandControl c = pc.command("cat /etc/*release").start()) {
var text = c.readOnlyStdout();
if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", null);
@ -104,7 +116,7 @@ public interface OsType {
}
String type = "Unknown";
try (CommandProcessControl c = pc.command("uname -o").start()) {
try (CommandControl c = pc.command("uname -o").start()) {
var text = c.readOnlyStdout();
if (c.getExitCode() == 0) {
type = text.strip();
@ -112,7 +124,7 @@ public interface OsType {
}
String version = "?";
try (CommandProcessControl c = pc.command("uname -r").start()) {
try (CommandControl c = pc.command("uname -r").start()) {
var text = c.readOnlyStdout();
if (c.getExitCode() == 0) {
version = text.strip();
@ -126,7 +138,12 @@ public interface OsType {
static class MacOs implements OsType {
@Override
public String getTempDirectory(ShellProcessControl pc) throws Exception {
public String getHomeDirectory(ShellControl pc) throws Exception {
return pc.executeStringSimpleCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("HOME"));
}
@Override
public String getTempDirectory(ShellControl pc) throws Exception {
return pc.executeStringSimpleCommand(pc.getShellDialect().getPrintVariableCommand("TMPDIR"));
}
@ -141,8 +158,8 @@ public interface OsType {
}
@Override
public Map<String, String> getProperties(ShellProcessControl pc) throws Exception {
try (CommandProcessControl c =
public Map<String, String> getProperties(ShellControl pc) throws Exception {
try (CommandControl c =
pc.subShell(ShellDialects.BASH).command("sw_vers").start()) {
var text = c.readOrThrow();
return PropertiesFormatsParser.parse(text, ":");
@ -150,7 +167,7 @@ public interface OsType {
}
@Override
public String determineOperatingSystemName(ShellProcessControl pc) throws Exception {
public String determineOperatingSystemName(ShellControl pc) throws Exception {
var properties = getProperties(pc);
var name = pc.executeStringSimpleCommand(
"awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup "

View file

@ -18,18 +18,18 @@ public abstract class ProcessControlProvider {
.toList();
}
public static ShellProcessControl createLocal() {
public static ShellControl createLocal(boolean stoppable) {
return INSTANCES.stream()
.map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl())
.map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl(stoppable))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow();
}
public static ShellProcessControl createSub(
ShellProcessControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> commandFunction,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand) {
public static ShellControl createSub(
ShellControl parent,
@NonNull FailableFunction<ShellControl, String, Exception> commandFunction,
FailableBiFunction<ShellControl, String, String, Exception> terminalCommand) {
return INSTANCES.stream()
.map(localProcessControlProvider ->
localProcessControlProvider.sub(parent, commandFunction, terminalCommand))
@ -38,10 +38,10 @@ public abstract class ProcessControlProvider {
.orElseThrow();
}
public static CommandProcessControl createCommand(
ShellProcessControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand) {
public static CommandControl createCommand(
ShellControl parent,
@NonNull FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellControl, String, Exception> terminalCommand) {
return INSTANCES.stream()
.map(localProcessControlProvider ->
localProcessControlProvider.command(parent, command, terminalCommand))
@ -50,7 +50,7 @@ public abstract class ProcessControlProvider {
.orElse(null);
}
public static ShellProcessControl createSsh(Object sshStore) {
public static ShellControl createSsh(Object sshStore) {
return INSTANCES.stream()
.map(localProcessControlProvider -> localProcessControlProvider.createSshControl(sshStore))
.filter(Objects::nonNull)
@ -58,17 +58,17 @@ public abstract class ProcessControlProvider {
.orElseThrow();
}
public abstract ShellProcessControl sub(
ShellProcessControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> commandFunction,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand);
public abstract ShellControl sub(
ShellControl parent,
@NonNull FailableFunction<ShellControl, String, Exception> commandFunction,
FailableBiFunction<ShellControl, String, String, Exception> terminalCommand);
public abstract CommandProcessControl command(
ShellProcessControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand);
public abstract CommandControl command(
ShellControl parent,
@NonNull FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellControl, String, Exception> terminalCommand);
public abstract ShellProcessControl createLocalProcessControl();
public abstract ShellControl createLocalProcessControl(boolean stoppable);
public abstract ShellProcessControl createSshControl(Object sshStore);
public abstract ShellControl createSshControl(Object sshStore);
}

View file

@ -9,13 +9,12 @@ import java.io.IOException;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import java.util.function.Predicate;
public interface ShellProcessControl extends ProcessControl {
public interface ShellControl extends ProcessControl {
Semaphore getCommandLock();
void onInit(Consumer<ShellProcessControl> pc);
void onInit(Consumer<ShellControl> pc);
String prepareTerminalOpen() throws Exception;
@ -26,25 +25,25 @@ public interface ShellProcessControl extends ProcessControl {
public void checkRunning() throws Exception;
default String executeStringSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) {
try (CommandControl c = command(command).start()) {
return c.readOrThrow();
}
}
default boolean executeBooleanSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) {
try (CommandControl c = command(command).start()) {
return c.discardAndCheckExit();
}
}
default void executeSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) {
try (CommandControl c = command(command).start()) {
c.discardOrThrow();
}
}
default void executeSimpleCommand(String command, String failMessage) throws Exception {
try (CommandProcessControl c = command(command).start()) {
try (CommandControl c = command(command).start()) {
c.discardOrThrow();
} catch (ProcessOutputException out) {
throw ProcessOutputException.of(failMessage, out);
@ -63,52 +62,52 @@ public interface ShellProcessControl extends ProcessControl {
OsType getOsType();
ShellProcessControl elevated(Predicate<ShellProcessControl> elevationFunction);
ShellControl elevated(FailableFunction<ShellControl, Boolean, Exception> elevationFunction);
ShellProcessControl elevation(SecretValue value);
ShellControl elevationPassword(SecretValue value);
ShellProcessControl initWith(List<String> cmds);
ShellControl initWith(List<String> cmds);
SecretValue getElevationPassword();
default ShellProcessControl subShell(@NonNull ShellDialect type) {
default ShellControl subShell(@NonNull ShellDialect type) {
return subShell(p -> type.getNormalOpenCommand(), (shellProcessControl, s) -> {
return s == null ? type.getNormalOpenCommand() : type.executeCommandWithShell(s);
})
.elevation(getElevationPassword());
.elevationPassword(getElevationPassword());
}
default ShellProcessControl subShell(@NonNull List<String> command) {
default ShellControl subShell(@NonNull List<String> command) {
return subShell(
shellProcessControl -> shellProcessControl.getShellDialect().flatten(command), null);
}
default ShellProcessControl subShell(@NonNull String command) {
default ShellControl subShell(@NonNull String command) {
return subShell(processControl -> command, null);
}
ShellProcessControl subShell(
FailableFunction<ShellProcessControl, String, Exception> command,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand);
ShellControl subShell(
FailableFunction<ShellControl, String, Exception> command,
FailableBiFunction<ShellControl, String, String, Exception> terminalCommand);
void executeLine(String command) throws Exception;
void cd(String directory) throws Exception;
@Override
ShellProcessControl start() throws Exception;
ShellControl start() throws Exception;
CommandProcessControl command(FailableFunction<ShellProcessControl, String, Exception> command);
CommandControl command(FailableFunction<ShellControl, String, Exception> command);
CommandProcessControl command(
FailableFunction<ShellProcessControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand);
CommandControl command(
FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellControl, String, Exception> terminalCommand);
default CommandProcessControl command(String command) {
default CommandControl command(String command) {
return command(shellProcessControl -> command);
}
default CommandProcessControl command(List<String> command) {
default CommandControl command(List<String> command) {
return command(shellProcessControl -> shellProcessControl.getShellDialect().flatten(command));
}

View file

@ -29,9 +29,9 @@ public interface ShellDialect {
String addInlineVariablesToCommand(Map<String, String> variables, String command);
Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellProcessControl control, String dir) throws Exception;
Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception;
Stream<String> listRoots(ShellProcessControl control) throws Exception;
Stream<String> listRoots(ShellControl control) throws Exception;
String getPauseCommand();
@ -101,7 +101,7 @@ public interface ShellDialect {
String getFileMoveCommand(String oldFile, String newFile);
CommandProcessControl getStreamFileWriteCommand(ShellProcessControl processControl, String file);
CommandControl getStreamFileWriteCommand(ShellControl processControl, String file);
String getTextFileWriteCommand(String content, String file);
@ -113,7 +113,7 @@ public interface ShellDialect {
String getWhichCommand(String executable);
Charset determineCharset(ShellProcessControl control) throws Exception;
Charset determineCharset(ShellControl control) throws Exception;
NewLine getNewLine();

View file

@ -1,6 +1,6 @@
package io.xpipe.core.store;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.CommandControl;
public interface CommandExecutionStore extends DataStore, LaunchableStore {
@ -9,5 +9,5 @@ public interface CommandExecutionStore extends DataStore, LaunchableStore {
return create().prepareTerminalOpen();
}
CommandProcessControl create() throws Exception;
CommandControl create() throws Exception;
}

View file

@ -1,7 +1,7 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import lombok.Getter;
import java.io.IOException;
@ -15,19 +15,19 @@ import java.util.stream.Stream;
public class ConnectionFileSystem implements FileSystem {
@JsonIgnore
private final ShellProcessControl shellProcessControl;
private final ShellControl shellControl;
@JsonIgnore
private final ShellStore store;
public ConnectionFileSystem(ShellProcessControl shellProcessControl, ShellStore store) {
this.shellProcessControl = shellProcessControl;
public ConnectionFileSystem(ShellControl shellControl, ShellStore store) {
this.shellControl = shellControl;
this.store = store;
}
@Override
public List<String> listRoots() throws Exception {
return shellProcessControl.getShellDialect().listRoots(shellProcessControl).toList();
return shellControl.getShellDialect().listRoots(shellControl).toList();
}
@Override
@ -35,7 +35,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public Stream<FileEntry> listFiles(String file) throws Exception {
return shellProcessControl.getShellDialect().listFiles(this, shellProcessControl, file);
return shellControl.getShellDialect().listFiles(this, shellControl, file);
}
@Override
@ -44,33 +44,33 @@ public class ConnectionFileSystem implements FileSystem {
}
@Override
public Optional<ShellProcessControl> getShell() {
return Optional.of(shellProcessControl);
public Optional<ShellControl> getShell() {
return Optional.of(shellControl);
}
@Override
public FileSystem open() throws Exception {
shellProcessControl.start();
shellControl.start();
return this;
}
@Override
public InputStream openInput(String file) throws Exception {
return shellProcessControl.command(proc ->
return shellControl.command(proc ->
proc.getShellDialect().getFileReadCommand(proc.getOsType().normalizeFileName(file)))
.startExternalStdout();
}
@Override
public OutputStream openOutput(String file) throws Exception {
return shellProcessControl.getShellDialect()
.getStreamFileWriteCommand(shellProcessControl, shellProcessControl.getOsType().normalizeFileName(file))
return shellControl.getShellDialect()
.getStreamFileWriteCommand(shellControl, shellControl.getOsType().normalizeFileName(file))
.startExternalStdin();
}
@Override
public boolean exists(String file) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getFileExistsCommand(proc.getOsType().normalizeFileName(file)))
.start()) {
return pc.discardAndCheckExit();
@ -79,7 +79,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public void delete(String file) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getFileDeleteCommand(proc.getOsType().normalizeFileName(file)))
.start()) {
pc.discardOrThrow();
@ -88,7 +88,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public void copy(String file, String newFile) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getFileCopyCommand(proc.getOsType().normalizeFileName(file), proc.getOsType().normalizeFileName(newFile))).complex()
.start()) {
pc.discardOrThrow();
@ -97,7 +97,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public void move(String file, String newFile) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getFileMoveCommand(proc.getOsType().normalizeFileName(file), proc.getOsType().normalizeFileName(newFile))).complex()
.start()) {
pc.discardOrThrow();
@ -106,7 +106,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public boolean mkdirs(String file) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.flatten(proc.getShellDialect()
.getMkdirsCommand(proc.getOsType().normalizeFileName(file))))
.start()) {
@ -116,7 +116,7 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public void touch(String file) throws Exception {
try (var pc = shellProcessControl.command(proc -> proc.getShellDialect()
try (var pc = shellControl.command(proc -> proc.getShellDialect()
.getFileTouchCommand(proc.getOsType().normalizeFileName(file))).complex()
.start()) {
pc.discardOrThrow();
@ -125,6 +125,6 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public void close() throws IOException {
shellProcessControl.close();
shellControl.close();
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.core.store;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import lombok.NonNull;
import lombok.Value;
@ -44,7 +44,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
FileSystemStore getStore();
Optional<ShellProcessControl> getShell();
Optional<ShellControl> getShell();
FileSystem open() throws Exception;

View file

@ -3,7 +3,7 @@ package io.xpipe.core.store;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.nio.charset.Charset;
@ -27,7 +27,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
return create().prepareTerminalOpen();
}
default ShellProcessControl create() {
default ShellControl create() {
var pc = createControl();
pc.onInit(processControl -> {
setState("type", processControl.getShellDialect());
@ -49,7 +49,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
return getState("charset", Charset.class, null);
}
ShellProcessControl createControl();
ShellControl createControl();
public default ShellDialect determineType() throws Exception {
try (var pc = create().start()) {
@ -59,7 +59,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
@Override
default void validate() throws Exception {
try (ShellProcessControl pc = create().start()) {}
try (ShellControl pc = create().start()) {}
}
public default String queryMachineName() throws Exception {

View file

@ -1,6 +1,6 @@
package io.xpipe.core.util;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.util.Optional;
import java.util.ServiceLoader;
@ -19,7 +19,7 @@ public abstract class ProxyManagerProvider {
return INSTANCE;
}
public abstract Optional<String> checkCompatibility(ShellProcessControl pc) throws Exception;
public abstract Optional<String> checkCompatibility(ShellControl pc) throws Exception;
public abstract boolean setup(ShellProcessControl pc) throws Exception;
public abstract boolean setup(ShellControl pc) throws Exception;
}

View file

@ -1,10 +1,10 @@
package io.xpipe.core.util;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ProcessOutputException;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import lombok.SneakyThrows;
import java.nio.charset.StandardCharsets;
@ -153,21 +153,21 @@ public class XPipeInstallation {
return v;
}
public static String queryInstallationVersion(ShellProcessControl p, String exec) throws Exception {
try (CommandProcessControl c = p.command(List.of(exec, "version")).start()) {
public static String queryInstallationVersion(ShellControl p, String exec) throws Exception {
try (CommandControl c = p.command(List.of(exec, "version")).start()) {
return c.readOrThrow();
} catch (ProcessOutputException ex) {
return "?";
}
}
public static String getInstallationExecutable(ShellProcessControl p, String installation) throws Exception {
public static String getInstallationExecutable(ShellControl p, String installation) throws Exception {
var executable = getDaemonExecutablePath(p.getOsType());
var file = FileNames.join(installation, executable);
return file;
}
public static String getDataBasePath(ShellProcessControl p) throws Exception {
public static String getDataBasePath(ShellControl p) throws Exception {
if (p.getOsType().equals(OsType.WINDOWS)) {
var base = p.executeStringSimpleCommand(p.getShellDialect().getPrintVariableCommand("userprofile"));
return FileNames.join(base, ".xpipe");
@ -218,7 +218,7 @@ public class XPipeInstallation {
return path;
}
public static String getDefaultInstallationBasePath(ShellProcessControl p, boolean acceptPortable)
public static String getDefaultInstallationBasePath(ShellControl p, boolean acceptPortable)
throws Exception {
if (acceptPortable) {
var customHome = p.executeStringSimpleCommand(p.getShellDialect().getPrintVariableCommand("XPIPE_HOME"));

View file

@ -2,7 +2,7 @@ package io.xpipe.core.util;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellControl;
import java.io.IOException;
import java.nio.file.Path;
@ -19,7 +19,7 @@ public class XPipeTempDirectory {
}
}
public static String get(ShellProcessControl proc) throws Exception {
public static String get(ShellControl proc) throws Exception {
var base = proc.getOsType().getTempDirectory(proc);
var dir = FileNames.join(base, "xpipe");
@ -36,7 +36,7 @@ public class XPipeTempDirectory {
return dir;
}
public static void clear(ShellProcessControl proc) throws Exception {
public static void clear(ShellControl proc) throws Exception {
var dir = get(proc);
if (!proc.executeBooleanSimpleCommand(proc.getShellDialect().getFileDeleteCommand(dir))) {
throw new IOException("Unable to delete temporary directory " + dir);