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 { run {
systemProperty 'io.xpipe.app.mode', 'gui' 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.writeLogs', "true"
systemProperty 'io.xpipe.app.writeSysOut', "true" systemProperty 'io.xpipe.app.writeSysOut', "true"
systemProperty 'io.xpipe.app.developerMode', "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.app.util.*;
import io.xpipe.core.impl.FileStore; import io.xpipe.core.impl.FileStore;
import io.xpipe.core.process.OsType; 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 io.xpipe.core.store.FileSystem;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
@ -73,7 +73,7 @@ final class FileContextMenu extends ContextMenu {
var execute = new MenuItem("Run in terminal"); var execute = new MenuItem("Run in terminal");
execute.setOnAction(event -> { execute.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
ShellProcessControl pc = ShellControl pc =
model.getFileSystem().getShell().orElseThrow(); model.getFileSystem().getShell().orElseThrow();
pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath())); pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath()));
var cmd = pc.command("\"" + entry.getPath() + "\"").prepareTerminalOpen(); var cmd = pc.command("\"" + entry.getPath() + "\"").prepareTerminalOpen();
@ -86,7 +86,7 @@ final class FileContextMenu extends ContextMenu {
var executeInBackground = new MenuItem("Run in background"); var executeInBackground = new MenuItem("Run in background");
executeInBackground.setOnAction(event -> { executeInBackground.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
ShellProcessControl pc = ShellControl pc =
model.getFileSystem().getShell().orElseThrow(); model.getFileSystem().getShell().orElseThrow();
pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath())); pc.executeBooleanSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath()));
var cmd = ScriptHelper.createDetachCommand(pc, "\"" + 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 var current = !(fileSystem instanceof LocalStore) && fs instanceof ConnectionFileSystem connectionFileSystem
? connectionFileSystem ? connectionFileSystem
.getShellProcessControl() .getShellControl()
.executeStringSimpleCommand(connectionFileSystem .executeStringSimpleCommand(connectionFileSystem
.getShellProcessControl() .getShellControl()
.getShellDialect() .getShellDialect()
.getPrintWorkingDirectoryCommand()) .getPrintWorkingDirectoryCommand())
: null; : null;
@ -199,7 +199,7 @@ final class OpenFileSystemModel {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
BusyProperty.execute(busy, () -> { BusyProperty.execute(busy, () -> {
if (store.getValue() instanceof ShellStore s) { if (store.getValue() instanceof ShellStore s) {
var connection = ((ConnectionFileSystem) fileSystem).getShellProcessControl(); var connection = ((ConnectionFileSystem) fileSystem).getShellControl();
var command = s.create() var command = s.create()
.initWith(List.of(connection.getShellDialect().getCdCommand(directory))) .initWith(List.of(connection.getShellDialect().getCdCommand(directory)))
.prepareTerminalOpen(); .prepareTerminalOpen();

View file

@ -8,6 +8,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -16,7 +17,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; 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> shown;
private final ObservableList<T> all; private final ObservableList<T> all;
@ -29,7 +30,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
} }
@Override @Override
public CompStructure<VBox> createBase() { public CompStructure<ScrollPane> createBase() {
Map<T, Region> cache = new HashMap<>(); Map<T, Region> cache = new HashMap<>();
VBox listView = new VBox(); VBox listView = new VBox();
@ -46,7 +47,11 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
cache.keySet().retainAll(c.getList()); 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) { 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 @Override
protected Region createSimple() { protected Region createSimple() {
var vbox = new VBox(); var vbox = new VBox();
vbox.setSpacing(8);
vbox.getStyleClass().add("content"); vbox.getStyleClass().add("content");
for (var v : values) { for (var v : values) {
var cb = new CheckBox(null); var cb = new CheckBox(null);

View file

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

View file

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

View file

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

View file

@ -72,7 +72,7 @@ public class AppWindowHelper {
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2); 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(() -> { ThreadHelper.runAsync(() -> {
var r = showBlockingAlert(c); var r = showBlockingAlert(c);
if (bt != null) { 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.app.issue.ErrorEvent;
import io.xpipe.core.impl.LocalStore; import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType; 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.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -37,7 +37,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
} }
protected Optional<Path> getApplicationPath() { protected Optional<Path> getApplicationPath() {
try (ShellProcessControl pc = LocalStore.getShell().start()) { try (ShellControl pc = LocalStore.getShell().start()) {
try (var c = pc.command(String.format( try (var c = pc.command(String.format(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister " "/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", + "-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() { public boolean isAvailable() {
try (ShellProcessControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalStore.getShell()) {
return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable)); return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); 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.app.util.MacOsPermissions;
import io.xpipe.core.impl.LocalStore; import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import java.util.List; import java.util.List;
@ -133,7 +133,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
public void launch(String name, String command) throws Exception { 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()) var suffix = command.equals(pc.getShellDialect().getNormalOpenCommand())
? "\"\"" ? "\"\""
: "\"" + command.replaceAll("\"", "\\\\\"") + "\""; : "\"" + command.replaceAll("\"", "\\\\\"") + "\"";
@ -187,7 +187,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
public void launch(String name, String command) throws Exception { public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalStore.getShell()) {
var cmd = String.format( var cmd = String.format(
""" """
osascript - "$@" <<EOF osascript - "$@" <<EOF
@ -225,7 +225,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return; return;
} }
try (ShellProcessControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalStore.getShell()) {
var cmd = String.format( var cmd = String.format(
""" """
osascript - "$@" <<EOF osascript - "$@" <<EOF
@ -258,7 +258,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
public void launch(String name, String command) throws Exception { public void launch(String name, String command) throws Exception {
try (ShellProcessControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalStore.getShell()) {
ApplicationHelper.checkSupport(pc, executable, displayName); ApplicationHelper.checkSupport(pc, executable, displayName);
var toExecute = executable + " " + toCommand(name, command); var toExecute = executable + " " + toCommand(name, command);
@ -274,7 +274,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
protected abstract String toCommand(String name, String command); protected abstract String toCommand(String name, String command);
public boolean isAvailable() { public boolean isAvailable() {
try (ShellProcessControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalStore.getShell()) {
return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable)); return pc.executeBooleanSimpleCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();

View file

@ -53,6 +53,8 @@ public abstract class DataStorage {
INSTANCE = shouldPersist() ? new StandardStorage() : new ImpersistentStorage(); INSTANCE = shouldPersist() ? new StandardStorage() : new ImpersistentStorage();
INSTANCE.load(); INSTANCE.load();
INSTANCE.storeEntries.forEach(entry -> entry.simpleRefresh());
DataStoreProviders.getAll().forEach(dataStoreProvider -> { DataStoreProviders.getAll().forEach(dataStoreProvider -> {
try { try {
dataStoreProvider.storageInit(); dataStoreProvider.storageInit();
@ -115,6 +117,21 @@ public abstract class DataStorage {
return createUniqueSourceEntryName(col, typeName); 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) { private String createUniqueSourceEntryName(DataSourceCollection col, String base) {
base = DataSourceId.cleanString(base); base = DataSourceId.cleanString(base);
var id = DataSourceId.create(col != null ? col.getName() : null, 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) { 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); addStore(e);
return 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.ScriptHelper;
import io.xpipe.app.util.TerminalHelper; import io.xpipe.app.util.TerminalHelper;
import io.xpipe.core.impl.FileNames; 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.OsType;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
@ -22,7 +22,7 @@ import java.util.List;
public class AppInstaller { 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 asset = getSuitablePlatformAsset(s);
var file = AppDownloads.downloadInstaller(asset, version); var file = AppDownloads.downloadInstaller(asset, version);
installFile(s, asset, file); installFile(s, asset, file);
@ -32,14 +32,14 @@ public class AppInstaller {
asset.installLocal(localFile.toString()); 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; String targetFile = null;
if (s.isLocal()) { if (s.isLocal()) {
targetFile = localFile.toString(); targetFile = localFile.toString();
} else { } else {
targetFile = FileNames.join( targetFile = FileNames.join(
s.getTemporaryDirectory(), localFile.getFileName().toString()); s.getTemporaryDirectory(), localFile.getFileName().toString());
try (CommandProcessControl c = s.getShellDialect().getStreamFileWriteCommand(s, targetFile) try (CommandControl c = s.getShellDialect().getStreamFileWriteCommand(s, targetFile)
.start()) { .start()) {
c.discardOut(); c.discardOut();
c.discardErr(); c.discardErr();
@ -73,13 +73,13 @@ public class AppInstaller {
throw new AssertionError(); 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)) { if (p.getOsType().equals(OsType.WINDOWS)) {
return new InstallerAssetType.Msi(); return new InstallerAssetType.Msi();
} }
if (p.getOsType().equals(OsType.LINUX)) { 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()) { .start()) {
return c.discardAndCheckExit() ? new InstallerAssetType.Debian() : new InstallerAssetType.Rpm(); return c.discardAndCheckExit() ? new InstallerAssetType.Debian() : new InstallerAssetType.Rpm();
} }
@ -102,7 +102,7 @@ public class AppInstaller {
}) })
public abstract static class InstallerAssetType { 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; public abstract void installLocal(String file) throws Exception;
@ -121,11 +121,11 @@ public class AppInstaller {
} }
@Override @Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception { public void installRemote(ShellControl shellControl, String file) throws Exception {
var exec = XPipeInstallation.getInstallationExecutable( var exec = XPipeInstallation.getInstallationExecutable(
shellProcessControl, shellControl,
XPipeInstallation.getDefaultInstallationBasePath(shellProcessControl, false)); XPipeInstallation.getDefaultInstallationBasePath(shellControl, false));
var logsDir = FileNames.join(XPipeInstallation.getDataBasePath(shellProcessControl), "logs"); var logsDir = FileNames.join(XPipeInstallation.getDataBasePath(shellControl), "logs");
var cmd = new ArrayList<>(java.util.List.of( var cmd = new ArrayList<>(java.util.List.of(
"start", "start",
"/wait", "/wait",
@ -139,7 +139,7 @@ public class AppInstaller {
exec exec
// "/qf" // "/qf"
)); ));
try (CommandProcessControl c = shellProcessControl.command(cmd).start()) { try (CommandControl c = shellControl.command(cmd).start()) {
c.discardOrThrow(); c.discardOrThrow();
} }
} }
@ -177,14 +177,14 @@ public class AppInstaller {
} }
@Override @Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception { public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) { try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command("DEBIAN_FRONTEND=noninteractive apt-get remove -qy xpipe") try (CommandControl c = pc.command("DEBIAN_FRONTEND=noninteractive apt-get remove -qy xpipe")
.elevated() .elevated()
.start()) { .start()) {
c.discardOrThrow(); c.discardOrThrow();
} }
try (CommandProcessControl c = pc.command( try (CommandControl c = pc.command(
"DEBIAN_FRONTEND=noninteractive apt-get install -qy \"" + file + "\"") "DEBIAN_FRONTEND=noninteractive apt-get install -qy \"" + file + "\"")
.elevated() .elevated()
.start()) { .start()) {
@ -214,9 +214,9 @@ public class AppInstaller {
} }
@Override @Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception { public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) { try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command("rpm -U -v --force \"" + file + "\"") try (CommandControl c = pc.command("rpm -U -v --force \"" + file + "\"")
.elevated() .elevated()
.start()) { .start()) {
c.discardOrThrow(); c.discardOrThrow();
@ -244,9 +244,9 @@ public class AppInstaller {
} }
@Override @Override
public void installRemote(ShellProcessControl shellProcessControl, String file) throws Exception { public void installRemote(ShellControl shellControl, String file) throws Exception {
try (var pc = shellProcessControl.subShell(ShellDialects.BASH).start()) { try (var pc = shellControl.subShell(ShellDialects.BASH).start()) {
try (CommandProcessControl c = pc.command( try (CommandControl c = pc.command(
"installer -verboseR -allowUntrusted -pkg \"" + file + "\" -target /") "installer -verboseR -allowUntrusted -pkg \"" + file + "\" -target /")
.elevated() .elevated()
.start()) { .start()) {

View file

@ -38,7 +38,7 @@ public class UpdateChangelogAlert {
alert.getDialogPane().setContent(markdown); alert.getDialogPane().setContent(markdown);
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE)); alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
}, },null,
r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {})); 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.app.issue.TrackEvent;
import io.xpipe.core.impl.LocalStore; import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import java.io.IOException; import java.io.IOException;
import java.util.List; 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( return processControl.executeBooleanSimpleCommand(
processControl.getShellDialect().getWhichCommand(executable)); 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 { throws Exception {
if (!isInPath(processControl, executable)) { if (!isInPath(processControl, executable)) {
throw new IOException(displayName + " executable " + executable + " not found in PATH"); 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.getDialogPane().setContent(AppWindowHelper.alertContentText(AppI18n.get("permissionsAlertTitleContent")));
a.getButtonTypes().clear(); a.getButtonTypes().clear();
alert.set(a); alert.set(a);
}, buttonType -> { }, null, buttonType -> {
alert.get().close(); alert.get().close();
if (buttonType.isEmpty() || !buttonType.get().getButtonData().isDefaultButton()) { if (buttonType.isEmpty() || !buttonType.get().getButtonData().isDefaultButton()) {
state.set(false); 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.AppDownloads;
import io.xpipe.app.update.AppInstaller; import io.xpipe.app.update.AppInstaller;
import io.xpipe.core.impl.FileNames; 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.ModuleHelper;
import io.xpipe.core.util.ProxyManagerProvider; import io.xpipe.core.util.ProxyManagerProvider;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
@ -31,7 +31,7 @@ public class ProxyManagerProviderImpl extends ProxyManagerProvider {
} }
@Override @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(); var version = ModuleHelper.isImage() ? AppProperties.get().getVersion() : AppDownloads.getLatestVersion();
if (AppPrefs.get().developerDisableConnectorInstallationVersionCheck().get()) { if (AppPrefs.get().developerDisableConnectorInstallationVersionCheck().get()) {
@ -54,7 +54,7 @@ public class ProxyManagerProviderImpl extends ProxyManagerProvider {
} }
@Override @Override
public boolean setup(ShellProcessControl s) throws Exception { public boolean setup(ShellControl s) throws Exception {
var message = checkCompatibility(s); var message = checkCompatibility(s);
if (message.isPresent()) { if (message.isPresent()) {
if (showAlert()) { 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.OsType;
import io.xpipe.core.process.ShellDialect; import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellDialects; 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 io.xpipe.core.util.SecretValue;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -15,7 +15,7 @@ import java.util.Random;
public class ScriptHelper { 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)) { if (pc.getOsType().equals(OsType.WINDOWS)) {
return "\"" + file + "\""; return "\"" + file + "\"";
} else if (pc.getOsType().equals(OsType.LINUX)){ } 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)) { if (pc.getOsType().equals(OsType.WINDOWS)) {
return "start \"\" " + command; return "start \"\" " + command;
} else { } else {
@ -123,7 +123,7 @@ public class ScriptHelper {
} }
public static String constructOpenWithInitScriptCommand( public static String constructOpenWithInitScriptCommand(
ShellProcessControl processControl, List<String> init, String toExecuteInShell) { ShellControl processControl, List<String> init, String toExecuteInShell) {
ShellDialect t = processControl.getShellDialect(); ShellDialect t = processControl.getShellDialect();
if (init.size() == 0 && toExecuteInShell == null) { if (init.size() == 0 && toExecuteInShell == null) {
return t.getNormalOpenCommand(); return t.getNormalOpenCommand();
@ -168,12 +168,12 @@ public class ScriptHelper {
} }
@SneakyThrows @SneakyThrows
public static String getExecScriptFile(ShellProcessControl processControl) { public static String getExecScriptFile(ShellControl processControl) {
return getExecScriptFile(processControl, processControl.getShellDialect().getScriptFileEnding()); return getExecScriptFile(processControl, processControl.getShellDialect().getScriptFileEnding());
} }
@SneakyThrows @SneakyThrows
public static String getExecScriptFile(ShellProcessControl processControl, String fileEnding) { public static String getExecScriptFile(ShellControl processControl, String fileEnding) {
var fileName = "exec-" + getScriptId(); var fileName = "exec-" + getScriptId();
var temp = processControl.getTemporaryDirectory(); var temp = processControl.getTemporaryDirectory();
var file = FileNames.join(temp, fileName + "." + fileEnding); var file = FileNames.join(temp, fileName + "." + fileEnding);
@ -181,7 +181,7 @@ public class ScriptHelper {
} }
@SneakyThrows @SneakyThrows
public static String createExecScript(ShellProcessControl processControl, String content) { public static String createExecScript(ShellControl processControl, String content) {
var fileName = "exec-" + getScriptId(); var fileName = "exec-" + getScriptId();
ShellDialect type = processControl.getShellDialect(); ShellDialect type = processControl.getShellDialect();
var temp = processControl.getTemporaryDirectory(); var temp = processControl.getTemporaryDirectory();
@ -190,7 +190,7 @@ public class ScriptHelper {
} }
@SneakyThrows @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(); ShellDialect type = processControl.getShellDialect();
content = type.prepareScriptContent(content); content = type.prepareScriptContent(content);
@ -207,7 +207,7 @@ public class ScriptHelper {
} }
@SneakyThrows @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 content = type.getScriptEchoCommand(pass.getSecretValue());
var temp = parent.getTemporaryDirectory(); var temp = parent.getTemporaryDirectory();
var file = FileNames.join(temp, "askpass-" + getScriptId() + "." + type.getScriptFileEnding()); var file = FileNames.join(temp, "askpass-" + getScriptId() + "." + type.getScriptFileEnding());

View file

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

View file

@ -10,7 +10,11 @@ mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$ hostFeatureUnsupported=Host does not support the feature $FEATURE$
missingStore=$NAME$ does not exist missingStore=$NAME$ does not exist
connectionName=Connection name
connectionNameDescription=Give this connection a custom name
unknown=Unknown 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 namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available noInformationAvailable=No information available

View file

@ -10,4 +10,13 @@
.choice-pane-comp { .choice-pane-comp {
-fx-spacing: 7px; -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 com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellDialects; 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.ConnectionFileSystem;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
@ -15,12 +15,12 @@ import java.nio.file.Path;
@JsonTypeName("local") @JsonTypeName("local")
public class LocalStore extends JacksonizedValue implements ShellStore { public class LocalStore extends JacksonizedValue implements ShellStore {
private static ShellProcessControl local; private static ShellControl local;
private static FileSystem localFileSystem; private static FileSystem localFileSystem;
public static ShellProcessControl getShell() throws Exception { public static ShellControl getShell() throws Exception {
if (local == null) { if (local == null) {
local = new LocalStore().create().start(); local = ProcessControlProvider.createLocal(false).start();
} }
return local; return local;
@ -128,7 +128,7 @@ public class LocalStore extends JacksonizedValue implements ShellStore {
} }
@Override @Override
public ShellProcessControl createControl() { public ShellControl createControl() {
return ProcessControlProvider.createLocal(); return ProcessControlProvider.createLocal(true);
} }
} }

View file

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

View file

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

View file

@ -18,18 +18,18 @@ public abstract class ProcessControlProvider {
.toList(); .toList();
} }
public static ShellProcessControl createLocal() { public static ShellControl createLocal(boolean stoppable) {
return INSTANCES.stream() return INSTANCES.stream()
.map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl()) .map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl(stoppable))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
} }
public static ShellProcessControl createSub( public static ShellControl createSub(
ShellProcessControl parent, ShellControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> commandFunction, @NonNull FailableFunction<ShellControl, String, Exception> commandFunction,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand) { FailableBiFunction<ShellControl, String, String, Exception> terminalCommand) {
return INSTANCES.stream() return INSTANCES.stream()
.map(localProcessControlProvider -> .map(localProcessControlProvider ->
localProcessControlProvider.sub(parent, commandFunction, terminalCommand)) localProcessControlProvider.sub(parent, commandFunction, terminalCommand))
@ -38,10 +38,10 @@ public abstract class ProcessControlProvider {
.orElseThrow(); .orElseThrow();
} }
public static CommandProcessControl createCommand( public static CommandControl createCommand(
ShellProcessControl parent, ShellControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> command, @NonNull FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand) { FailableFunction<ShellControl, String, Exception> terminalCommand) {
return INSTANCES.stream() return INSTANCES.stream()
.map(localProcessControlProvider -> .map(localProcessControlProvider ->
localProcessControlProvider.command(parent, command, terminalCommand)) localProcessControlProvider.command(parent, command, terminalCommand))
@ -50,7 +50,7 @@ public abstract class ProcessControlProvider {
.orElse(null); .orElse(null);
} }
public static ShellProcessControl createSsh(Object sshStore) { public static ShellControl createSsh(Object sshStore) {
return INSTANCES.stream() return INSTANCES.stream()
.map(localProcessControlProvider -> localProcessControlProvider.createSshControl(sshStore)) .map(localProcessControlProvider -> localProcessControlProvider.createSshControl(sshStore))
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -58,17 +58,17 @@ public abstract class ProcessControlProvider {
.orElseThrow(); .orElseThrow();
} }
public abstract ShellProcessControl sub( public abstract ShellControl sub(
ShellProcessControl parent, ShellControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> commandFunction, @NonNull FailableFunction<ShellControl, String, Exception> commandFunction,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand); FailableBiFunction<ShellControl, String, String, Exception> terminalCommand);
public abstract CommandProcessControl command( public abstract CommandControl command(
ShellProcessControl parent, ShellControl parent,
@NonNull FailableFunction<ShellProcessControl, String, Exception> command, @NonNull FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand); 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.List;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate;
public interface ShellProcessControl extends ProcessControl { public interface ShellControl extends ProcessControl {
Semaphore getCommandLock(); Semaphore getCommandLock();
void onInit(Consumer<ShellProcessControl> pc); void onInit(Consumer<ShellControl> pc);
String prepareTerminalOpen() throws Exception; String prepareTerminalOpen() throws Exception;
@ -26,25 +25,25 @@ public interface ShellProcessControl extends ProcessControl {
public void checkRunning() throws Exception; public void checkRunning() throws Exception;
default String executeStringSimpleCommand(String command) throws Exception { default String executeStringSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
return c.readOrThrow(); return c.readOrThrow();
} }
} }
default boolean executeBooleanSimpleCommand(String command) throws Exception { default boolean executeBooleanSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
return c.discardAndCheckExit(); return c.discardAndCheckExit();
} }
} }
default void executeSimpleCommand(String command) throws Exception { default void executeSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
c.discardOrThrow(); c.discardOrThrow();
} }
} }
default void executeSimpleCommand(String command, String failMessage) throws Exception { default void executeSimpleCommand(String command, String failMessage) throws Exception {
try (CommandProcessControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
c.discardOrThrow(); c.discardOrThrow();
} catch (ProcessOutputException out) { } catch (ProcessOutputException out) {
throw ProcessOutputException.of(failMessage, out); throw ProcessOutputException.of(failMessage, out);
@ -63,52 +62,52 @@ public interface ShellProcessControl extends ProcessControl {
OsType getOsType(); 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(); SecretValue getElevationPassword();
default ShellProcessControl subShell(@NonNull ShellDialect type) { default ShellControl subShell(@NonNull ShellDialect type) {
return subShell(p -> type.getNormalOpenCommand(), (shellProcessControl, s) -> { return subShell(p -> type.getNormalOpenCommand(), (shellProcessControl, s) -> {
return s == null ? type.getNormalOpenCommand() : type.executeCommandWithShell(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( return subShell(
shellProcessControl -> shellProcessControl.getShellDialect().flatten(command), null); shellProcessControl -> shellProcessControl.getShellDialect().flatten(command), null);
} }
default ShellProcessControl subShell(@NonNull String command) { default ShellControl subShell(@NonNull String command) {
return subShell(processControl -> command, null); return subShell(processControl -> command, null);
} }
ShellProcessControl subShell( ShellControl subShell(
FailableFunction<ShellProcessControl, String, Exception> command, FailableFunction<ShellControl, String, Exception> command,
FailableBiFunction<ShellProcessControl, String, String, Exception> terminalCommand); FailableBiFunction<ShellControl, String, String, Exception> terminalCommand);
void executeLine(String command) throws Exception; void executeLine(String command) throws Exception;
void cd(String directory) throws Exception; void cd(String directory) throws Exception;
@Override @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( CommandControl command(
FailableFunction<ShellProcessControl, String, Exception> command, FailableFunction<ShellControl, String, Exception> command,
FailableFunction<ShellProcessControl, String, Exception> terminalCommand); FailableFunction<ShellControl, String, Exception> terminalCommand);
default CommandProcessControl command(String command) { default CommandControl command(String command) {
return command(shellProcessControl -> command); return command(shellProcessControl -> command);
} }
default CommandProcessControl command(List<String> command) { default CommandControl command(List<String> command) {
return command(shellProcessControl -> shellProcessControl.getShellDialect().flatten(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); 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(); String getPauseCommand();
@ -101,7 +101,7 @@ public interface ShellDialect {
String getFileMoveCommand(String oldFile, String newFile); String getFileMoveCommand(String oldFile, String newFile);
CommandProcessControl getStreamFileWriteCommand(ShellProcessControl processControl, String file); CommandControl getStreamFileWriteCommand(ShellControl processControl, String file);
String getTextFileWriteCommand(String content, String file); String getTextFileWriteCommand(String content, String file);
@ -113,7 +113,7 @@ public interface ShellDialect {
String getWhichCommand(String executable); String getWhichCommand(String executable);
Charset determineCharset(ShellProcessControl control) throws Exception; Charset determineCharset(ShellControl control) throws Exception;
NewLine getNewLine(); NewLine getNewLine();

View file

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

View file

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

View file

@ -1,7 +1,7 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -44,7 +44,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
FileSystemStore getStore(); FileSystemStore getStore();
Optional<ShellProcessControl> getShell(); Optional<ShellControl> getShell();
FileSystem open() throws Exception; 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.impl.LocalStore;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect; import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -27,7 +27,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
return create().prepareTerminalOpen(); return create().prepareTerminalOpen();
} }
default ShellProcessControl create() { default ShellControl create() {
var pc = createControl(); var pc = createControl();
pc.onInit(processControl -> { pc.onInit(processControl -> {
setState("type", processControl.getShellDialect()); setState("type", processControl.getShellDialect());
@ -49,7 +49,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
return getState("charset", Charset.class, null); return getState("charset", Charset.class, null);
} }
ShellProcessControl createControl(); ShellControl createControl();
public default ShellDialect determineType() throws Exception { public default ShellDialect determineType() throws Exception {
try (var pc = create().start()) { try (var pc = create().start()) {
@ -59,7 +59,7 @@ public interface ShellStore extends DataStore, StatefulDataStore, LaunchableStor
@Override @Override
default void validate() throws Exception { default void validate() throws Exception {
try (ShellProcessControl pc = create().start()) {} try (ShellControl pc = create().start()) {}
} }
public default String queryMachineName() throws Exception { public default String queryMachineName() throws Exception {

View file

@ -1,6 +1,6 @@
package io.xpipe.core.util; package io.xpipe.core.util;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import java.util.Optional; import java.util.Optional;
import java.util.ServiceLoader; import java.util.ServiceLoader;
@ -19,7 +19,7 @@ public abstract class ProxyManagerProvider {
return INSTANCE; 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; package io.xpipe.core.util;
import io.xpipe.core.impl.FileNames; 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.OsType;
import io.xpipe.core.process.ProcessOutputException; import io.xpipe.core.process.ProcessOutputException;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -153,21 +153,21 @@ public class XPipeInstallation {
return v; return v;
} }
public static String queryInstallationVersion(ShellProcessControl p, String exec) throws Exception { public static String queryInstallationVersion(ShellControl p, String exec) throws Exception {
try (CommandProcessControl c = p.command(List.of(exec, "version")).start()) { try (CommandControl c = p.command(List.of(exec, "version")).start()) {
return c.readOrThrow(); return c.readOrThrow();
} catch (ProcessOutputException ex) { } catch (ProcessOutputException ex) {
return "?"; 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 executable = getDaemonExecutablePath(p.getOsType());
var file = FileNames.join(installation, executable); var file = FileNames.join(installation, executable);
return file; return file;
} }
public static String getDataBasePath(ShellProcessControl p) throws Exception { public static String getDataBasePath(ShellControl p) throws Exception {
if (p.getOsType().equals(OsType.WINDOWS)) { if (p.getOsType().equals(OsType.WINDOWS)) {
var base = p.executeStringSimpleCommand(p.getShellDialect().getPrintVariableCommand("userprofile")); var base = p.executeStringSimpleCommand(p.getShellDialect().getPrintVariableCommand("userprofile"));
return FileNames.join(base, ".xpipe"); return FileNames.join(base, ".xpipe");
@ -218,7 +218,7 @@ public class XPipeInstallation {
return path; return path;
} }
public static String getDefaultInstallationBasePath(ShellProcessControl p, boolean acceptPortable) public static String getDefaultInstallationBasePath(ShellControl p, boolean acceptPortable)
throws Exception { throws Exception {
if (acceptPortable) { if (acceptPortable) {
var customHome = p.executeStringSimpleCommand(p.getShellDialect().getPrintVariableCommand("XPIPE_HOME")); 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.impl.FileNames;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellControl;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; 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 base = proc.getOsType().getTempDirectory(proc);
var dir = FileNames.join(base, "xpipe"); var dir = FileNames.join(base, "xpipe");
@ -36,7 +36,7 @@ public class XPipeTempDirectory {
return dir; return dir;
} }
public static void clear(ShellProcessControl proc) throws Exception { public static void clear(ShellControl proc) throws Exception {
var dir = get(proc); var dir = get(proc);
if (!proc.executeBooleanSimpleCommand(proc.getShellDialect().getFileDeleteCommand(dir))) { if (!proc.executeBooleanSimpleCommand(proc.getShellDialect().getFileDeleteCommand(dir))) {
throw new IOException("Unable to delete temporary directory " + dir); throw new IOException("Unable to delete temporary directory " + dir);