Try to fix push

This commit is contained in:
crschnick 2023-02-01 09:16:26 +00:00
parent 8478992798
commit 94c77ab1c6
51 changed files with 907 additions and 218 deletions

View file

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

View file

@ -80,8 +80,8 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
pane.setCenter(r);
});
pane.setCenter(selected.getValue().comp().createRegion());
pane.setPrefWidth(1200);
pane.setPrefHeight(700);
pane.setPrefWidth(1280);
pane.setPrefHeight(720);
AppFont.normal(pane);
return new SimpleCompStructure<>(pane);
}

View file

@ -5,16 +5,12 @@ import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.CompStructure;
import io.xpipe.extension.fxcomps.util.PlatformThread;
import io.xpipe.extension.fxcomps.util.SimpleChangeListener;
import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import lombok.Builder;
import lombok.Value;
@ -80,17 +76,6 @@ public class LazyTextFieldComp extends Comp<LazyTextFieldComp.Structure> {
currentValue.setValue(newValue);
});
Animation delay = new PauseTransition(Duration.millis(800));
delay.setOnFinished(e -> {
r.setDisable(false);
r.requestFocus();
});
sp.addEventFilter(MouseEvent.MOUSE_ENTERED, e -> {
delay.playFromStart();
});
sp.addEventFilter(MouseEvent.MOUSE_EXITED, e -> {
delay.stop();
});
r.focusedProperty().addListener((c, o, n) -> {
if (!n) {
r.setDisable(true);

View file

@ -0,0 +1,77 @@
package io.xpipe.app.comp.base;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.CompStructure;
import io.xpipe.extension.fxcomps.SimpleCompStructure;
import io.xpipe.extension.fxcomps.util.BindingsHelper;
import io.xpipe.extension.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ListBoxViewComp<T> extends Comp<CompStructure<VBox>> {
private final ObservableList<T> shown;
private final ObservableList<T> all;
private final Function<T, Comp<?>> compFunction;
public ListBoxViewComp(
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction) {
this.shown = PlatformThread.sync(shown);
this.all = PlatformThread.sync(all);
this.compFunction = compFunction;
}
@Override
public CompStructure<VBox> createBase() {
Map<T, Region> cache = new HashMap<>();
VBox listView = new VBox();
listView.setFocusTraversable(false);
refresh(listView, shown, cache, false);
listView.requestLayout();
shown.addListener((ListChangeListener<? super T>) (c) -> {
refresh(listView, c.getList(), cache, true);
});
all.addListener((ListChangeListener<? super T>) c -> {
cache.keySet().retainAll(c.getList());
});
return new SimpleCompStructure<>(listView);
}
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
Runnable update = () -> {
var newShown = c.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, compFunction.apply(v).createRegion());
}
return cache.get(v);
})
.toList();
if (!listView.getChildren().equals(newShown)) {
BindingsHelper.setContent(listView.getChildren(), newShown);
listView.layout();
}
};
if (asynchronous) {
Platform.runLater(update);
} else {
PlatformThread.runLaterIfNeeded(update);
}
}
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp.storage.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
@ -10,6 +11,7 @@ import io.xpipe.extension.I18n;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.SimpleComp;
import io.xpipe.extension.fxcomps.SimpleCompStructure;
import io.xpipe.extension.fxcomps.augment.GrowAugment;
import io.xpipe.extension.fxcomps.augment.PopupMenuAugment;
import io.xpipe.extension.fxcomps.impl.FancyTooltipAugment;
@ -100,6 +102,9 @@ public class StoreEntryComp extends SimpleComp {
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
var storeIcon = imageComp.createRegion();
storeIcon.getStyleClass().add("icon");
if (entry.getState().getValue().isUsable()) {
new FancyTooltipAugment<>(new SimpleStringProperty(entry.getEntry().getProvider().getDisplayName())).augment(storeIcon);
}
return storeIcon;
}
@ -117,6 +122,7 @@ public class StoreEntryComp extends SimpleComp {
var storeIcon = createIcon();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
@ -133,7 +139,7 @@ public class StoreEntryComp extends SimpleComp {
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-comp");
grid.getStyleClass().add("store-entry-grid");
grid.setOnMouseClicked(event -> {
if (entry.getEditable().get()) {
@ -143,7 +149,12 @@ public class StoreEntryComp extends SimpleComp {
applyState(grid);
return grid;
var button = new JFXButton();
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.setMaxWidth(2000);
return button;
}
private Comp<?> createButtonBar() {

View file

@ -4,7 +4,6 @@ import io.xpipe.app.comp.base.ListViewComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.SimpleComp;
import io.xpipe.extension.fxcomps.augment.GrowAugment;
import io.xpipe.extension.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableBooleanValue;
@ -15,14 +14,18 @@ import java.util.Map;
public class StoreEntryListComp extends SimpleComp {
private Comp<?> createList() {
var topLevel = StoreEntrySection.createTopLevels();
var filtered = BindingsHelper.filteredContentBinding(
topLevel,
StoreViewState.get().getFilterString().map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
var content = new ListViewComp<>(
StoreViewState.get().getShownEntries(),
StoreViewState.get().getAllEntries(),
filtered,
topLevel,
null,
(StoreEntryWrapper e) -> {
return new StoreEntryComp(e).apply(GrowAugment.create(true, false));
(StoreEntrySection e) -> {
return e.comp(true);
});
return content;
return content.styleClass("store-list-comp");
}
@Override

View file

@ -0,0 +1,120 @@
package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.augment.GrowAugment;
import io.xpipe.extension.fxcomps.impl.HorizontalComp;
import io.xpipe.extension.fxcomps.impl.VerticalComp;
import io.xpipe.extension.fxcomps.util.BindingsHelper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import org.kordamp.ikonli.javafx.FontIcon;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
public class StoreEntrySection implements StorageFilter.Filterable {
public StoreEntrySection(StoreEntryWrapper entry, ObservableList<StoreEntrySection> children) {
this.entry = entry;
this.children = children;
}
public static ObservableList<StoreEntrySection> createTopLevels() {
var topLevel = BindingsHelper.mappedContentBinding(
StoreViewState.get()
.getAllEntries()
.filtered(storeEntryWrapper ->
!storeEntryWrapper.getEntry().getState().isUsable()
|| storeEntryWrapper
.getEntry()
.getProvider()
.getParent(storeEntryWrapper
.getEntry()
.getStore())
== null),
storeEntryWrapper -> create(storeEntryWrapper));
var ordered = BindingsHelper.orderedContentBinding(
topLevel,
Comparator.<StoreEntrySection, Instant>comparing(storeEntrySection ->
storeEntrySection.entry.lastAccessProperty().getValue())
.reversed());
return ordered;
}
public static StoreEntrySection create(StoreEntryWrapper e) {
if (!e.getEntry().getState().isUsable()) {
return new StoreEntrySection(e, FXCollections.observableArrayList());
}
var children = BindingsHelper.mappedContentBinding(
StoreViewState.get()
.getAllEntries()
.filtered(other -> other.getEntry().getState().isUsable()
&& e.getEntry()
.getStore()
.equals(other.getEntry()
.getProvider()
.getParent(other.getEntry().getStore()))),
entry1 -> create(entry1));
var ordered = BindingsHelper.orderedContentBinding(
children,
Comparator.<StoreEntrySection, Instant>comparing(storeEntrySection ->
storeEntrySection.entry.lastAccessProperty().getValue())
.reversed());
return new StoreEntrySection(e, ordered);
}
private final StoreEntryWrapper entry;
private final ObservableList<StoreEntrySection> children;
public Comp<?> comp(boolean top) {
var root = new StoreEntryComp(entry).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
var icon = Comp.of(() -> {
var padding = new FontIcon("mdal-arrow_forward_ios");
padding.setIconSize(14);
var pain = new StackPane(padding);
pain.setMinWidth(20);
pain.setMaxHeight(20);
return pain;
});
List<Comp<?>> topEntryList = top ? List.of(root) : List.of(icon, root);
if (children.size() == 0) {
return new HorizontalComp(topEntryList);
}
var all = BindingsHelper.orderedContentBinding(
children,
Comparator.comparing(storeEntrySection ->
storeEntrySection.entry.lastAccessProperty().getValue()));
var shown = BindingsHelper.filteredContentBinding(
all,
StoreViewState.get().getFilterString().map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
var content = new ListBoxViewComp<>(shown, all, (StoreEntrySection e) -> {
return e.comp(false).apply(GrowAugment.create(true, false));
})
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS))
.apply(struc -> struc.get().backgroundProperty().set(Background.fill(Color.color(0, 0, 0, 0.01))));
var spacer = Comp.of(() -> {
var padding = new Region();
padding.setMinWidth(25);
padding.setMaxWidth(25);
return padding;
});
return new VerticalComp(List.of(
new HorizontalComp(topEntryList),
new HorizontalComp(List.of(spacer, content))
.apply(struc -> struc.get().setFillHeight(true))));
}
@Override
public boolean shouldShow(String filter) {
return entry.shouldShow(filter)
|| children.stream().anyMatch(storeEntrySection -> storeEntrySection.shouldShow(filter));
}
}

View file

@ -11,6 +11,7 @@ import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -95,6 +96,10 @@ public class StoreViewState {
return filter;
}
public ObservableValue<String> getFilterString() {
return filter.filterProperty();
}
public ObservableList<StoreEntryWrapper> getAllEntries() {
return allEntries;
}

View file

@ -78,6 +78,14 @@ public class App extends Application {
focus();
});
appWindow.show();
// For demo purposes
if (true) {
stage.setX(310);
stage.setY(178);
stage.setWidth(1300);
stage.setHeight(730);
}
}
public void focus() {

View file

@ -60,6 +60,7 @@ public class DataSourceCollection extends StorageElement {
var json = mapper.readTree(dir.resolve("collection.json").toFile());
var uuid = UUID.fromString(json.required("uuid").textValue());
var name = json.required("name").textValue();
Objects.requireNonNull(name);
var lastModified = Instant.parse(json.required("lastModified").textValue());
JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, UUID.class);

View file

@ -82,7 +82,7 @@ public abstract class DataStorage {
}
public DataSourceCollection getInternalCollection() {
var found = sourceCollections.stream().filter(o -> o.getName().equals("Internal")).findAny();
var found = sourceCollections.stream().filter(o -> o.getName() != null && o.getName().equals("Internal")).findAny();
if (found.isPresent()) {
return found.get();
}

View file

@ -17,16 +17,16 @@ public class DataStorageWriter {
public static JsonNode storeToNode(DataStore store) {
var mapper = JacksonMapper.newMapper();
var tree = mapper.valueToTree(store);
return replaceReferencesWithIds(store, tree);
return replaceReferencesWithIds(tree, true);
}
public static JsonNode sourceToNode(DataSource<?> source) {
var mapper = JacksonMapper.newMapper();
var tree = mapper.valueToTree(source);
return replaceReferencesWithIds(source, tree);
return replaceReferencesWithIds(tree, true);
}
private static JsonNode replaceReferencesWithIds(Object root, JsonNode node) {
private static JsonNode replaceReferencesWithIds(JsonNode node, boolean isRoot) {
var mapper = JacksonMapper.newMapper();
node = replaceReferencesWithIds(
@ -38,7 +38,7 @@ public class DataStorageWriter {
try {
var store = mapper.treeToValue(possibleReference, DataStore.class);
if (root == null || !root.equals(store)) {
if (!isRoot) {
var found = DataStorage.get().getEntryByStore(store);
return found.map(dataSourceEntry -> dataSourceEntry.getUuid());
}
@ -46,14 +46,14 @@ public class DataStorageWriter {
}
return Optional.empty();
},
"storeId");
"storeId", isRoot);
node = replaceReferencesWithIds(
node,
possibleReference -> {
try {
var source = mapper.treeToValue(possibleReference, DataSource.class);
if (root == null || !root.equals(source)) {
if (!isRoot) {
var found = DataStorage.get().getEntryBySource(source);
return found.map(dataSourceEntry -> dataSourceEntry.getUuid());
}
@ -61,13 +61,13 @@ public class DataStorageWriter {
}
return Optional.empty();
},
"sourceId");
"sourceId", isRoot);
return node;
}
private static JsonNode replaceReferencesWithIds(
JsonNode node, Function<JsonNode, Optional<UUID>> function, String key) {
JsonNode node, Function<JsonNode, Optional<UUID>> function, String key, boolean isRoot) {
if (!node.isObject()) {
return node;
}
@ -80,7 +80,7 @@ public class DataStorageWriter {
var replacement = JsonNodeFactory.instance.objectNode();
node.fields().forEachRemaining(stringJsonNodeEntry -> {
var resolved = replaceReferencesWithIds(null, stringJsonNodeEntry.getValue());
var resolved = replaceReferencesWithIds(stringJsonNodeEntry.getValue(), false);
replacement.set(stringJsonNodeEntry.getKey(), resolved);
});
return replacement;

View file

@ -75,3 +75,7 @@
-fx-padding: 0;
-fx-focus-color: transparent;
}
.store-list-comp .list-cell {
-fx-padding: 0;
}

View file

@ -1,27 +1,30 @@
.store-entry-comp .date, .store-entry-comp .summary {
.store-entry-grid .date, .store-entry-grid .summary {
-fx-text-fill: -xp-text-light;
}
.store-entry-comp:failed .jfx-text-field {
.store-entry-grid:failed .jfx-text-field {
-fx-text-fill: red;
}
.store-entry-comp:incomplete .jfx-text-field {
.store-entry-grid:incomplete .jfx-text-field {
-fx-text-fill: gray;
}
.store-entry-comp:incomplete .summary {
.store-entry-grid:incomplete .summary {
-fx-text-fill: gray;
}
.store-entry-comp:incomplete .information {
.store-entry-grid:incomplete .information {
-fx-text-fill: gray;
}
.store-entry-comp:incomplete .date {
.store-entry-grid:incomplete .date {
-fx-text-fill: gray;
}
.store-entry-comp:incomplete .icon {
.store-entry-grid:incomplete .icon {
-fx-opacity: 0.5;
}
.store-entry-comp {
-fx-padding: 6px;
}

View file

@ -248,6 +248,7 @@ productionTest {
// + System.getProperty("java.vm.version") + ")")
graalvmNative {
testSupport = false
binaries {
main {
imageName = 'xpipe' // The name of the native image, defaults to the project name
@ -261,8 +262,6 @@ graalvmNative {
'-Dpicocli.converters.excludes=java.time.*,java.sql.*'
)
buildArgs.addAll(
'--static',
'--libc=musl',
// '--debug-attach=8000',
'--enable-https',
'--install-exit-handlers',
@ -279,6 +278,13 @@ graalvmNative {
'org.ocpsoft.prettytime,' +
'com.fasterxml.jackson')
if (org.gradle.internal.os.OperatingSystem.current().isLinux()) {
buildArgs.addAll(
'--static',
'--libc=musl'
)
}
// Build Time
systemProperties(prodProperties)
}

44
cli/musl-setup.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/sh
# This script builds the bundle inside the Docker container and then copies it to the host machine.
set -e
# Set up the URL to the version of musl used by alpine at the time of writing.
LATEST_MUSL_URL="http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz"
# Set up the URL for the latest zlib version available at the time of writing.
LATEST_ZLIB_URL="https://www.zlib.net/zlib-1.2.13.tar.gz"
BUNDLE_DIR_NAME="bundle"
DIR="$1"
BUILD_DIR="$DIR/build/musl"
# Create the folder that will contain the finished bundle.
mkdir -p $BUILD_DIR
cd $BUILD_DIR
echo "Downloading musl library."
wget "${LATEST_MUSL_URL}"
echo "Downloading zlib library."
wget "${LATEST_ZLIB_URL}"
# Grab the names of the archives from the URL.
MUSL_TAR="${LATEST_MUSL_URL##*/}"
ZLIB_TAR="${LATEST_ZLIB_URL##*/}"
# Compile musl, compiling only the static musl libraries.
echo "Extracting musl."
tar xvzf "$MUSL_TAR"
MUSL_DIR=$(tar tzf "${MUSL_TAR}" | cut -d'/' -f1 | uniq)
TOOLCHAIN_DIR="${BUILD_DIR=}/${MUSL_DIR}"
echo $TOOLCHAIN_DIR
# Compile zlib with the musl library we just built.
export CC="${TOOLCHAIN_DIR}/bin/gcc"
echo "Extracting zlib."
tar xvzf "${ZLIB_TAR}"
ZLIB_DIR=$(tar tzf "${ZLIB_TAR}" | cut -d'/' -f1 | uniq)
cd "${ZLIB_DIR}"
echo "Configuring zlib."
echo $TOOLCHAIN_DIR
./configure --static --prefix="${TOOLCHAIN_DIR}"
echo "Building zlib."
make
make install

7
cli/native-build-musl.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
DIR="$1"
MUSL_DIR="$DIR/build/musl/x86_64-linux-musl-native"
export PATH="$PATH:$MUSL_DIR/bin"
"$DIR/../gradlew" :cli:nativeCompile "-Dorg.gradle.jvmargs=-Xmx2048M"

View file

@ -1,3 +1,3 @@
CALL "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
set JAVA_HOME=%GRAALVM_HOME%
"%~dp0\..\gradlew.bat" :cli:nativeCompile
"%~dp0\..\gradlew.bat" :cli:nativeCompile "-Dorg.gradle.jvmargs=-Xmx2048M"

View file

@ -11,11 +11,9 @@ import java.util.function.Predicate;
public interface ShellProcessControl extends ProcessControl {
default String prepareTerminalOpen() throws Exception {
return prepareTerminalOpen(null);
}
String prepareTerminalOpen() throws Exception;
String prepareTerminalOpen(String content) throws Exception;
String prepareIntermediateTerminalOpen(String content) throws Exception;
default String executeStringSimpleCommand(String command) throws Exception {
try (CommandProcessControl c = command(command).start()) {
@ -35,7 +33,7 @@ public interface ShellProcessControl extends ProcessControl {
}
}
default void executeSimpleCommand(String command,String failMessage) throws Exception {
default void executeSimpleCommand(String command, String failMessage) throws Exception {
try (CommandProcessControl c = command(command).start()) {
c.discardOrThrow();
} catch (ProcessOutputException out) {
@ -60,12 +58,14 @@ public interface ShellProcessControl extends ProcessControl {
ShellProcessControl elevation(SecretValue value);
ShellProcessControl initWith(List<String> cmds);
SecretValue getElevationPassword();
default ShellProcessControl subShell(@NonNull ShellType type) {
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());
}
@ -80,10 +80,9 @@ public interface ShellProcessControl extends ProcessControl {
ShellProcessControl subShell(
@NonNull Function<ShellProcessControl, String> command,
BiFunction<ShellProcessControl, String, String> terminalCommand
);
BiFunction<ShellProcessControl, String, String> terminalCommand);
void executeCommand(String command) throws Exception;
void executeLine(String command) throws Exception;
@Override
ShellProcessControl start() throws Exception;
@ -91,8 +90,7 @@ public interface ShellProcessControl extends ProcessControl {
CommandProcessControl command(Function<ShellProcessControl, String> command);
CommandProcessControl command(
Function<ShellProcessControl, String> command, Function<ShellProcessControl, String> terminalCommand
);
Function<ShellProcessControl, String> command, Function<ShellProcessControl, String> terminalCommand);
default CommandProcessControl command(String command) {
return command(shellProcessControl -> command);

View file

@ -3,7 +3,6 @@ package io.xpipe.core.process;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.charsetter.NewLine;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
@ -55,7 +54,7 @@ public interface ShellType {
return getEchoCommand(s, false);
}
String getSetVariableCommand(String variable, String value);
String getSetEnvironmentVariableCommand(String variable, String value);
String getEchoCommand(String s, boolean toErrorStream);
@ -67,8 +66,14 @@ public interface ShellType {
String getPrintVariableCommand(String prefix, String name);
default String getPrintEnvironmentVariableCommand(String name) {
return getPrintVariableCommand(name);
}
String getNormalOpenCommand();
String getInitFileOpenCommand(String file);
String executeCommandWithShell(String cmd);
List<String> executeCommandListWithShell(String cmd);
@ -79,7 +84,7 @@ public interface ShellType {
String getStreamFileWriteCommand(String file);
String getSimpleFileWriteCommand(String content, String file);
String getTextFileWriteCommand(String content, String file);
String getFileDeleteCommand(String file);

View file

@ -10,6 +10,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@ -57,7 +58,7 @@ public class ShellTypes {
}
@Override
public String getSetVariableCommand(String variableName, String value) {
public String getSetEnvironmentVariableCommand(String variableName, String value) {
return ("set \"" + variableName + "=" + value.replaceAll("\"", "^$0") + "\"");
}
@ -68,13 +69,13 @@ public class ShellTypes {
@Override
public String getEchoCommand(String s, boolean toErrorStream) {
return "(echo " + s + (toErrorStream ? ")1>&2" : ")");
return "(echo " + escapeStringValue(s).replaceAll("\r\n", "^\r\n") + (toErrorStream ? ")1>&2" : ")");
}
@Override
public String getScriptEchoCommand(String s) {
return ("@echo off\r\nset \"echov=" + escapeStringValue(s)
+ "\"\r\necho %echov%\r\n@echo on\n(goto) 2>nul & del \"%~f0\"");
return ("set \"echov=" + escapeStringValue(s)
+ "\"\r\necho %echov%\r\n@echo on\r\n(goto) 2>nul & del \"%~f0\"");
}
@Override
@ -103,7 +104,7 @@ public class ShellTypes {
@Override
public String prepareScriptContent(String content) {
return "@echo off\n" + content;
return "@echo off\r\n" + content;
}
@Override
@ -125,6 +126,11 @@ public class ShellTypes {
return "cmd";
}
@Override
public String getInitFileOpenCommand(String file) {
return "cmd /K \"" + file + "\"";
}
@Override
public String executeCommandWithShell(String cmd) {
return "cmd.exe /C " + cmd + "";
@ -151,8 +157,15 @@ public class ShellTypes {
}
@Override
public String getSimpleFileWriteCommand(String content, String file) {
return "echo " + content + " > \"" + file + "\"";
public String getTextFileWriteCommand(String content, String file) {
// if (true) return getEchoCommand(content, false) + " > \"" + file + "\"";
var command = new ArrayList<String>();
for (String line : content.split("(\n|\r\n)")) {
command.add("echo " + escapeStringValue(line) + ">> \"" + file + "\"");
command.add("echo." + ">> \"" + file + "\"");
}
return String.join("&", command);
}
@Override
@ -219,7 +232,7 @@ public class ShellTypes {
@Override
public void disableHistory(ShellProcessControl pc) throws Exception {
pc.executeCommand("Set-PSReadLineOption -HistorySaveStyle SaveNothing");
pc.executeLine("Set-PSReadLineOption -HistorySaveStyle SaveNothing");
}
@Override
@ -233,8 +246,8 @@ public class ShellTypes {
}
@Override
public String getSimpleFileWriteCommand(String content, String file) {
return "echo \"" + content + "\" | Out-File \"" + file + "\"";
public String getTextFileWriteCommand(String content, String file) {
return "echo \"" + content.replaceAll("(\n|\r\n)", "`n") + "\" | Out-File \"" + file + "\"";
}
@Override
@ -258,7 +271,12 @@ public class ShellTypes {
}
@Override
public String getSetVariableCommand(String variableName, String value) {
public String getPrintEnvironmentVariableCommand(String name) {
return "echo \"" + "$env:" + escapeStringValue(name) + "\"";
}
@Override
public String getSetEnvironmentVariableCommand(String variableName, String value) {
return "$env:" + variableName + " = \"" + escapeStringValue(value) + "\"";
}
@ -320,6 +338,11 @@ public class ShellTypes {
return "powershell /nologo";
}
@Override
public String getInitFileOpenCommand(String file) {
return "powershell.exe -NoExit -File \"" + file + "\"";
}
@Override
public String executeCommandWithShell(String cmd) {
return "powershell.exe -Command '" + cmd + "'";
@ -394,8 +417,13 @@ public class ShellTypes {
public abstract static class PosixBase implements ShellType {
@Override
public String getSimpleFileWriteCommand(String content, String file) {
return "echo \"" + content + "\" > \"" + file + "\"";
public String getInitFileOpenCommand(String file) {
return getName() + " --rcfile \"" + file + "\"";
}
@Override
public String getTextFileWriteCommand(String content, String file) {
return "echo -e '" + content.replaceAll("\n", "\\\\n").replaceAll("'","\\\\'") + "' > \"" + file + "\"";
}
@Override
@ -461,7 +489,7 @@ public class ShellTypes {
@Override
public void disableHistory(ShellProcessControl pc) throws Exception {
pc.executeCommand("unset HISTFILE");
pc.executeLine("unset HISTFILE");
}
@Override
@ -485,7 +513,7 @@ public class ShellTypes {
}
@Override
public String getSetVariableCommand(String variable, String value) {
public String getSetEnvironmentVariableCommand(String variable, String value) {
return "export " + variable + "=\"" + value + "\"";
}

View file

@ -79,9 +79,9 @@ public class XPipeInstallation {
public static Path getLocalDynamicLibraryDirectory() {
Path path = getLocalInstallationBasePath();
if (OsType.getLocal().equals(OsType.WINDOWS)) {
return path.resolve("runtime").resolve("bin");
return path.resolve("app").resolve("runtime").resolve("bin");
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return path.resolve("lib").resolve("runtime").resolve("lib");
return path.resolve("app").resolve("lib").resolve("runtime").resolve("lib");
} else {
return path.resolve("Contents")
.resolve("runtime")
@ -95,7 +95,7 @@ public class XPipeInstallation {
Path path = getLocalInstallationBasePath();
return OsType.getLocal().equals(OsType.MAC)
? path.resolve("Contents").resolve("Resources").resolve("extensions")
: path.resolve("extensions");
: path.resolve("app").resolve("extensions");
}
private static Path getLocalInstallationBasePathForJavaExecutable(Path executable) {
@ -108,9 +108,9 @@ public class XPipeInstallation {
.getParent()
.getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return executable.getParent().getParent().getParent().getParent();
return executable.getParent().getParent().getParent().getParent().getParent();
} else {
return executable.getParent().getParent().getParent();
return executable.getParent().getParent().getParent().getParent();
}
}
@ -118,9 +118,9 @@ public class XPipeInstallation {
if (OsType.getLocal().equals(OsType.MAC)) {
return executable.getParent().getParent().getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return executable.getParent().getParent();
return executable.getParent().getParent().getParent();
} else {
return executable.getParent();
return executable.getParent().getParent();
}
}

24
dist/cli.gradle vendored
View file

@ -3,8 +3,6 @@ import java.nio.file.Paths
import java.nio.file.StandardCopyOption
def distDir = "$buildDir/dist"
def windows = org.gradle.internal.os.OperatingSystem.current().isWindows();
apply plugin: 'org.asciidoctor.jvm.convert'
asciidoctor {
@ -29,21 +27,37 @@ task copyCompletion(type: Copy) {
into "$distDir/cli"
}
task setupMusl(type: Exec) {
commandLine "${project(':cli').projectDir.toString()}/musl-setup.sh", project(':cli').projectDir.toString()
}
task buildCli(type: DefaultTask) {
if (!org.gradle.internal.os.OperatingSystem.current().isWindows()) {
if (org.gradle.internal.os.OperatingSystem.current().isLinux()) {
dependsOn(setupMusl)
project(':cli').getTasksByName('nativeCompile', true).forEach(v->v.mustRunAfter(setupMusl))
}
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
dependsOn(project(':cli').getTasksByName('nativeCompile', true))
}
doLast {
if (windows) {
if (rootProject.os.isWindows()) {
exec {
executable project(':cli').projectDir.toString() + '/native-build.bat'
environment System.getenv()
}
}
if (rootProject.os.isLinux()) {
exec {
commandLine project(':cli').projectDir.toString() + '/native-build-musl.sh', project(':cli').projectDir.toString()
environment System.getenv()
}
}
Files.createDirectories(Paths.get(distDir, 'cli'))
def ending = windows ? ".exe" : ""
def ending = rootProject.os.isWindows() ? ".exe" : ""
var outputFile = Paths.get(project(':cli').buildDir.toString() + "/native/nativeCompile/xpipe$ending")
if (!Files.exists(outputFile)) {
throw new IOException("Cli output file does not exist")

View file

@ -1,5 +1,6 @@
package io.xpipe.ext.jdbc;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.Proxyable;
import java.sql.Connection;
@ -26,6 +27,8 @@ public interface JdbcBaseStore extends JdbcStore, Proxyable {
Map<String, String> createProperties();
ShellStore getProxy();
default Map<String, Object> createDefaultProperties() {
return Map.of();
}

View file

@ -1,6 +1,7 @@
package io.xpipe.ext.jdbc;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.ProxyProvider;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.util.DataStoreFormatter;
@ -20,6 +21,13 @@ public abstract class JdbcStoreProvider implements DataStoreProvider {
return true;
}
@Override
public DataStore getParent(DataStore store) {
JdbcBaseStore s = store.asNeeded();
return !ShellStore.isLocal(s.getProxy()) ? s.getProxy() : null;
}
@Override
public boolean init() throws Exception {
try {

View file

@ -60,7 +60,7 @@ public class PostgresPsqlAction implements DataStoreActionProvider<PostgresSimpl
var t = pc.getShellType();
String passwordPrefix = "";
if (store.getAuth() instanceof SimpleAuthMethod p && p.getPassword() != null) {
var passwordCommand = t.getSetVariableCommand(
var passwordCommand = t.getSetEnvironmentVariableCommand(
"PGPASSWORD",
p.getPassword().getSecretValue());
passwordPrefix = passwordCommand + "\n";

View file

@ -90,7 +90,7 @@ public class CommandProcessControlImpl extends ProcessControlImpl implements Com
: terminalCommand.apply(parent))
+ operator
+ parent.getShellType().getPauseCommand();
return parent.prepareTerminalOpen(consoleCommand);
return parent.prepareIntermediateTerminalOpen(consoleCommand);
}
}
@ -150,8 +150,8 @@ public class CommandProcessControlImpl extends ProcessControlImpl implements Com
parent.start();
var baseCommand = command.apply(parent);
this.complex = complex || baseCommand.contains("\n");
if (complex) {
var createExecScript = complex || baseCommand.contains("\n");
if (createExecScript) {
var script = ScriptHelper.createExecScript(parent, baseCommand, true);
baseCommand = "\"" + script + "\"";
}
@ -182,7 +182,7 @@ public class CommandProcessControlImpl extends ProcessControlImpl implements Com
if (elevated) {
string = ElevationHelper.elevateNormalCommand(string, parent, baseCommand);
}
parent.executeCommand(string);
parent.executeLine(string);
running = true;
// Check for custom charset

View file

@ -26,6 +26,12 @@ import java.util.List;
public class CommandStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
CommandStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;

View file

@ -20,6 +20,12 @@ import java.util.List;
public class DockerStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
DockerStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;

View file

@ -27,6 +27,11 @@ public class LocalProcessControlImpl extends ShellProcessControlImpl {
protected boolean stdinClosed;
private Process process;
@Override
public String prepareTerminalOpen() throws Exception {
return prepareIntermediateTerminalOpen(null);
}
public void closeStdin() throws IOException {
if (stdinClosed) {
return;
@ -99,15 +104,10 @@ public class LocalProcessControlImpl extends ShellProcessControlImpl {
}
@Override
public String prepareTerminalOpen(String content) throws Exception {
try (var ignored = start()) {
if (content == null) {
return getShellType().getNormalOpenCommand();
}
var file = ScriptHelper.createExecScript(this, content, false);
TrackEvent.withTrace("proc", "Writing open init script")
public String prepareIntermediateTerminalOpen(String content) throws Exception {
try (var pc = start()) {
var file = ScriptHelper.constructOpenWithInitScriptCommand(pc, initCommands, content);
TrackEvent.withDebug("proc", "Writing open init script")
.tag("file", file)
.tag("content", content)
.handle();
@ -150,6 +150,10 @@ public class LocalProcessControlImpl extends ShellProcessControlImpl {
charset = shellType.determineCharset(this);
osType = OsType.getLocal();
for (String s : initCommands) {
executeLine(s);
}
return this;
}

View file

@ -10,6 +10,7 @@ import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.prefs.PrefsHandler;
import io.xpipe.extension.prefs.PrefsProvider;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
@ -17,11 +18,18 @@ import java.util.List;
public class ProcPrefs extends PrefsProvider {
private final BooleanProperty enableCaching = new SimpleBooleanProperty(true);
public ObservableBooleanValue enableCaching() {
return enableCaching;
}
private final ObjectProperty<TerminalType> terminalType = new SimpleObjectProperty<>(TerminalType.getDefault());
private final SimpleListProperty<TerminalType> terminalTypeList = new SimpleListProperty<>(
FXCollections.observableArrayList(PrefsChoiceValue.getSupported(TerminalType.class)));
private final SingleSelectionField<TerminalType> terminalTypeControl =
Field.ofSingleSelectionType(terminalTypeList, terminalType).render(() -> new TranslatableComboBoxControl<>());
// Custom terminal
// ===============
private final StringProperty customTerminalCommand = new SimpleStringProperty("");

View file

@ -21,6 +21,12 @@ import java.util.List;
public class ShellCommandStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
ShellCommandStore s = store.asNeeded();
return s.getHost();
}
@Override
public boolean isShareable() {
return true;

View file

@ -0,0 +1,47 @@
package io.xpipe.ext.proc;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.extension.util.Validators;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@SuperBuilder
@Getter
@Jacksonized
@JsonTypeName("shellEnvironment")
public class ShellEnvironmentStore extends JacksonizedValue implements MachineStore {
private final String commands;
private final ShellStore host;
private final ShellType shell;
public ShellEnvironmentStore(String commands, ShellStore host, ShellType shell) {
this.commands = commands;
this.host = host;
this.shell = shell;
}
@Override
public void checkComplete() throws Exception {
Validators.nonNull(commands, "Commands");
Validators.nonNull(host, "Host");
Validators.namedStoreExists(host, "Host");
host.checkComplete();
}
@Override
public ShellProcessControl create() {
var pc = host.create();
if (shell != null) {
pc = pc.subShell(shell);
}
return pc.initWith(commands.lines().toList());
}
}

View file

@ -0,0 +1,99 @@
package io.xpipe.ext.proc;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.Identifiers;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.extension.GuiDialog;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.impl.ShellStoreChoiceComp;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import io.xpipe.extension.util.SimpleValidator;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import java.util.List;
public class ShellEnvironmentStoreProvider implements DataStoreProvider {
@Override
public boolean isShareable() {
return true;
}
@Override
public String getId() {
return "shellEnvironment";
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();
ShellEnvironmentStore st = store.getValue().asNeeded();
Property<ShellStore> hostProperty = new SimpleObjectProperty<>(st.getHost());
Property<String> commandProp = new SimpleObjectProperty<>(st.getCommands());
Property<ShellType> shellTypeProperty = new SimpleObjectProperty<>(st.getShell());
var q = new DynamicOptionsBuilder(I18n.observable("configuration"))
.addComp(I18n.observable("host"), ShellStoreChoiceComp.host(st, hostProperty), hostProperty)
.nonNull(val)
.addComp(
I18n.observable("proc.shellType"),
new ShellTypeChoiceComp(shellTypeProperty),
shellTypeProperty)
.addStringArea("proc.commands", commandProp, false)
.nonNull(val)
.bind(
() -> {
return new ShellEnvironmentStore(
commandProp.getValue(), hostProperty.getValue(), shellTypeProperty.getValue());
},
store)
.buildComp();
return new GuiDialog(q, val);
}
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
ShellEnvironmentStore s = store.asNeeded();
var name = s.getShell() != null ? s.getShell().getDisplayName() : "Default";
return I18n.get("shellEnvironment.informationFormat", name);
}
@Override
public String toSummaryString(DataStore store, int length) {
ShellEnvironmentStore s = store.asNeeded();
var local = ShellStore.isLocal(s.getHost());
var commandSummary = "<" + s.getCommands().lines().count() + " commands>";
return commandSummary;
}
@Override
public DataStore getParent(DataStore store) {
ShellEnvironmentStore s = store.asNeeded();
return s.getHost();
}
@Override
public Category getCategory() {
return Category.SHELL;
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(ShellEnvironmentStore.class);
}
@Override
public DataStore defaultStore() {
return new ShellEnvironmentStore(null, new LocalStore(), null);
}
@Override
public List<String> getPossibleNames() {
return Identifiers.get("shell", "environment");
}
}

View file

@ -1,12 +1,17 @@
package io.xpipe.ext.proc;
import io.xpipe.core.process.*;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.util.SecretValue;
import io.xpipe.ext.proc.util.ShellReader;
import lombok.Getter;
import lombok.NonNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
@ -16,6 +21,7 @@ public abstract class ShellProcessControlImpl extends ProcessControlImpl impleme
protected Integer startTimeout = 10000;
protected UUID uuid;
protected String command;
protected List<String> initCommands = new ArrayList<>();
@Getter
protected ShellType shellType;
@ -38,6 +44,12 @@ public abstract class ShellProcessControlImpl extends ProcessControlImpl impleme
return this;
}
@Override
public ShellProcessControl initWith(List<String> cmds) {
this.initCommands.addAll(cmds);
return this;
}
@Override
public ShellProcessControl subShell(
@NonNull Function<ShellProcessControl, String> command,
@ -57,7 +69,7 @@ public abstract class ShellProcessControlImpl extends ProcessControlImpl impleme
}
@Override
public void executeCommand(String command) throws Exception {
public void executeLine(String command) throws Exception {
writeLine(command);
if (getShellType().doesRepeatInput()) {
ShellReader.readLine(getStdout(), getCharset());

View file

@ -6,6 +6,7 @@ import io.xpipe.ext.proc.augment.SshCommandAugmentation;
import io.xpipe.ext.proc.util.ShellHelper;
import io.xpipe.ext.proc.util.SshToolHelper;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ScriptHelper;
import java.io.IOException;
@ -30,6 +31,11 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
this.elevationPassword = store.getPassword();
}
@Override
public String prepareTerminalOpen() throws Exception {
return prepareIntermediateTerminalOpen(null);
}
public void closeStdin() throws IOException {
if (stdinClosed) {
return;
@ -61,7 +67,7 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
}
@Override
public String prepareTerminalOpen(String content) throws Exception {
public String prepareIntermediateTerminalOpen(String content) throws Exception {
String script = null;
if (content != null) {
try (var pc = start()) {
@ -81,7 +87,7 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
var operator = pc.getShellType().getOrConcatenationOperator();
var consoleCommand = passwordCommand + operator + pc.getShellType().getPauseCommand();
return pc.prepareTerminalOpen(consoleCommand);
return pc.prepareIntermediateTerminalOpen(consoleCommand);
}
}
@ -115,10 +121,6 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
return false;
}
private int getConnectionHash() {
throw new UnsupportedOperationException();
}
@Override
public ShellProcessControl elevated(Predicate<ShellProcessControl> elevationFunction) {
throw new UnsupportedOperationException();
@ -157,27 +159,20 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
.tag("charset", charset.name())
.handle();
return this;
}
@Override
public void executeCommand(String command) throws Exception {
TrackEvent.withTrace("ssh", "Executing ssh command")
.tag("command", command)
.tag("previousChannelActive", channel != null)
.handle();
if (channel != null) {
channel.disconnect();
}
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
((ChannelExec) channel).setCommand(getShellType().getNormalOpenCommand());
((ChannelExec) channel).setPty(false);
stdout = channel.getInputStream();
stderr = ((ChannelExec) channel).getErrStream();
stdin = channel.getOutputStream();
channel.connect();
// Execute optional init commands
for (String s : initCommands) {
executeLine(s);
}
return this;
}
@Override
@ -197,9 +192,11 @@ public class SshProcessControlImpl extends ShellProcessControlImpl {
running = false;
stdinClosed = true;
shellType = null;
charset = null;
osType = null;
if (!PrefsProvider.get(ProcPrefs.class).enableCaching().get()) {
shellType = null;
charset = null;
command = null;
}
getStderr().close();
getStdin().close();

View file

@ -6,6 +6,8 @@ import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.SecretValue;
import io.xpipe.ext.proc.augment.SshCommandAugmentation;
import io.xpipe.ext.proc.util.SshToolHelper;
import io.xpipe.extension.util.Validators;
import lombok.AccessLevel;
import lombok.Getter;
@ -14,8 +16,6 @@ import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.nio.file.Path;
@SuperBuilder
@Jacksonized
@Getter
@ -50,7 +50,41 @@ public class SshStore extends JacksonizedValue implements MachineStore {
@Override
public ShellProcessControl create() {
return new SshProcessControlImpl(this);
if (ShellStore.isLocal(proxy)) {
return new SshProcessControlImpl(this);
}
return proxy.create()
.subShell(
shellProcessControl -> {
var command = SshToolHelper.toCommand(this, shellProcessControl);
var augmentedCommand = new SshCommandAugmentation().prepareNonTerminalCommand(shellProcessControl, command);
var passwordCommand = SshToolHelper.passPassword(
augmentedCommand,
getPassword(),
getKey() != null ? getKey().getPassword() : null,
shellProcessControl);
return passwordCommand;
},
(shellProcessControl, s) -> {
var command = SshToolHelper.toCommand(this, shellProcessControl);
var augmentedCommand = new SshCommandAugmentation().prepareTerminalCommand(shellProcessControl, command, s);
var passwordCommand = SshToolHelper.passPassword(
augmentedCommand,
getPassword(),
getKey() != null ? getKey().getPassword() : null,
shellProcessControl);
if (true) return passwordCommand;
var operator = shellProcessControl.getShellType().getOrConcatenationOperator();
var consoleCommand = passwordCommand + operator + shellProcessControl.getShellType().getPauseCommand();
try {
return shellProcessControl.prepareIntermediateTerminalOpen(consoleCommand);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.elevated(shellProcessControl -> true);
}
@SuperBuilder
@ -58,7 +92,7 @@ public class SshStore extends JacksonizedValue implements MachineStore {
@Getter
public static class SshKey {
@NonNull
Path file;
String file;
SecretValue password;
}

View file

@ -22,11 +22,16 @@ import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.nio.file.Path;
import java.util.List;
public class SshStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
SshStore s = store.asNeeded();
return !ShellStore.isLocal(s.getProxy()) ? s.getProxy() : null;
}
@Override
public boolean isShareable() {
return true;
@ -89,10 +94,9 @@ public class SshStoreProvider implements DataStoreProvider {
() -> {
return keyFileProperty.get() != null
? SshStore.SshKey.builder()
.file(Path.of(keyFileProperty
.file(keyFileProperty
.get()
.getFile()
.toString()))
.getFile())
.password(keyPasswordProperty.get())
.build()
: null;

View file

@ -5,6 +5,7 @@ import io.xpipe.ext.proc.util.ElevationHelper;
import io.xpipe.ext.proc.util.ShellHelper;
import io.xpipe.ext.proc.util.ShellReader;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ScriptHelper;
import lombok.NonNull;
@ -45,50 +46,60 @@ public class SubShellProcessControlImpl extends ShellProcessControlImpl {
}
@Override
public String prepareTerminalOpen(String content) throws Exception {
public String prepareTerminalOpen() throws Exception {
if (isRunning()) {
exitAndWait();
}
try (var parentPc = parent.start()) {
var operator = parent.getShellType().getOrConcatenationOperator();
var consoleCommand = this.terminalCommand.apply(parent, null);
var elevated = elevationFunction.test(parent);
if (elevated) {
consoleCommand = ElevationHelper.elevateTerminalCommand(consoleCommand, parent);
}
var openCommand = "";
try (var pc = start()) {
var initCommand = ScriptHelper.constructOpenWithInitScriptCommand(pc, initCommands, null);
openCommand = consoleCommand + " " + initCommand + operator
+ parent.getShellType().getPauseCommand();
TrackEvent.withDebug("proc", "Preparing for terminal open")
.tag("initCommand", initCommand)
.tag("openCommand", openCommand)
.handle();
}
return parent.prepareIntermediateTerminalOpen(openCommand);
}
}
@Override
public String prepareIntermediateTerminalOpen(String content) throws Exception {
if (this.terminalCommand == null) {
throw new UnsupportedOperationException("Terminal open not supported");
}
if (content == null) {
if (isRunning()) {
exitAndWait();
try (var pc = start()) {
var operator = parent.getShellType().getOrConcatenationOperator();
var file = ScriptHelper.createExecScript(this, content, false);
var terminalCommand = this.terminalCommand.apply(parent, file);
var elevated = elevationFunction.test(parent);
if (elevated) {
terminalCommand = ElevationHelper.elevateTerminalCommand(terminalCommand, parent);
}
try (var ignored = parent.start()) {
var operator = parent.getShellType().getOrConcatenationOperator();
var consoleCommand = this.terminalCommand.apply(parent, null);
var elevated = elevationFunction.test(parent);
if (elevated) {
consoleCommand = ElevationHelper.elevateTerminalCommand(consoleCommand, parent);
}
var openCommand =
consoleCommand + operator + parent.getShellType().getPauseCommand();
return parent.prepareTerminalOpen(openCommand);
}
} else {
try (var ignored = start()) {
var operator = parent.getShellType().getOrConcatenationOperator();
var file = ScriptHelper.createExecScript(this, content, false);
var initCommand = ScriptHelper.constructOpenWithInitScriptCommand(pc, initCommands, terminalCommand);
var openCommand = initCommand + operator + parent.getShellType().getPauseCommand();
TrackEvent.withDebug("proc", "Preparing for terminal open")
.tag("file", file)
.tag("content", ShellHelper.censor(content, sensitive))
.tag("openCommand", openCommand)
.tag("initCommand", initCommand)
.handle();
var consoleCommand = this.terminalCommand.apply(parent, file);
var elevated = elevationFunction.test(parent);
if (elevated) {
consoleCommand = ElevationHelper.elevateTerminalCommand(consoleCommand, parent);
}
var openCommand =
consoleCommand + operator + parent.getShellType().getPauseCommand();
TrackEvent.withTrace("proc", "Preparing for console open")
.tag("file", file)
.tag("content", ShellHelper.censor(content, sensitive))
.tag("openCommand", openCommand)
.handle();
exitAndWait();
parent.restart();
return parent.prepareTerminalOpen(openCommand);
}
exitAndWait();
return parent.prepareIntermediateTerminalOpen(openCommand);
}
}
@ -159,7 +170,7 @@ public class SubShellProcessControlImpl extends ShellProcessControlImpl {
if (elevated) {
commandToExecute = ElevationHelper.elevateNormalCommand(commandToExecute, parent, command);
}
parent.executeCommand(commandToExecute);
parent.executeLine(commandToExecute);
// Wait for prefix output
// In case this fails, we know that the whole command has not been executed, which can only happen if the syntax ever occurred
@ -170,20 +181,31 @@ public class SubShellProcessControlImpl extends ShellProcessControlImpl {
}
running = true;
shellType =
ShellHelper.determineType(this, parent.getCharset(), commandToExecute, uuid.toString(), startTimeout);
if (shellType == null) {
shellType = ShellHelper.determineType(
this, parent.getCharset(), commandToExecute, uuid.toString(), startTimeout);
}
shellType.disableHistory(this);
charset = shellType.determineCharset(this);
osType = ShellHelper.determineOsType(this);
if (charset == null) {
charset = shellType.determineCharset(this);
}
if (osType == null) {
osType = ShellHelper.determineOsType(this);
}
TrackEvent.withTrace("proc", "Detected shell environment...")
TrackEvent.withDebug("proc", "Detected shell environment...")
.tag("shellType", shellType.getName())
.tag("charset", charset.name())
.tag("osType", osType.getName())
.handle();
// Execute optional init commands
for (String s : initCommands) {
executeLine(s);
}
// Read all output until now
executeCommand(getShellType().getEchoCommand(uuid.toString(), false)
executeLine(getShellType().getEchoCommand(uuid.toString(), false)
+ getShellType().getConcatenationOperator()
+ getShellType().getEchoCommand(uuid.toString(), true));
ShellReader.readUntilOccurrence(getStdout(), getCharset(), uuid.toString(), commandToExecute, false);
@ -251,9 +273,11 @@ public class SubShellProcessControlImpl extends ShellProcessControlImpl {
running = false;
}
shellType = null;
charset = null;
if (!PrefsProvider.get(ProcPrefs.class).enableCaching().get()) {
shellType = null;
charset = null;
command = null;
}
uuid = null;
command = null;
}
}

View file

@ -20,6 +20,12 @@ import java.util.List;
public class WslStoreProvider implements DataStoreProvider {
@Override
public DataStore getParent(DataStore store) {
WslStore s = store.asNeeded();
return s.getHost();
}
@Override
public GuiDialog guiDialog(Property<DataStore> store) {
var val = new SimpleValidator();

View file

@ -5,7 +5,7 @@ import java.util.List;
public class PosixShellCommandAugmentation extends CommandAugmentation {
@Override
public boolean matches(String executable) {
return executable.equals("sh") || executable.equals("bash");
return executable.equals("sh") || executable.equals("bash") || executable.equals("zsh");
}
@Override

View file

@ -22,7 +22,7 @@ public class SshToolHelper {
store.getPort().toString()));
if (store.getKey() != null) {
list.add("-i");
list.add(store.getKey().getFile().toString());
list.add(store.getKey().getFile());
}
return parent.getShellType().flatten(list);
}

View file

@ -40,6 +40,7 @@ open module io.xpipe.ext.proc {
LaunchCommandAction;
provides DataStoreProvider with
SshStoreProvider,
ShellEnvironmentStoreProvider,
CommandStoreProvider,
DockerStoreProvider,
ShellCommandStoreProvider,

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -8,6 +8,9 @@ inMemory.displayName=In Memory
inMemory.displayDescription=Store binary data in memory
shellCommand.displayName=Shell Command
shellCommand.displayDescription=Open a shell with a custom command
shellEnvironment.displayName=Shell Environment
shellEnvironment.displayDescription=Run custom init commands on the shell connection
shellEnvironment.informationFormat=$TYPE$ environment
ssh.displayName=SSH Connection
ssh.displayDescription=Connect via SSH
binary.displayName=Binary
@ -28,6 +31,7 @@ configuration=Configuration
selectOutput=Select Output
options=Options
requiresElevation=Run Elevated
commands=Commands
selectStore=Select Store
selectSource=Select Source
commandLineRead=Read

View file

@ -7,6 +7,9 @@ import lombok.Getter;
import org.apache.commons.lang3.function.FailableConsumer;
import org.junit.jupiter.api.Assertions;
import java.util.List;
import java.util.UUID;
@Getter
public enum ShellCheckTestItem {
OS_NAME(shellProcessControl -> {
@ -22,9 +25,21 @@ public enum ShellCheckTestItem {
Assertions.assertEquals("world", s2);
}),
INIT_FILE(shellProcessControl -> {
var content = "<contentß>";
try (var c = shellProcessControl
.subShell(shellProcessControl.getShellType())
.initWith(List.of(shellProcessControl.getShellType().getSetEnvironmentVariableCommand("testVar", content)))
.start()) {
var output = c.executeStringSimpleCommand(
shellProcessControl.getShellType().getPrintEnvironmentVariableCommand("testVar"));
Assertions.assertEquals(content, output);
}
}),
STREAM_WRITE(shellProcessControl -> {
var content = "hello\nworldß";
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), "f1.txt");
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
try (var c = shellProcessControl
.command(shellProcessControl.getShellType().getStreamFileWriteCommand(fileOne))
.start()) {
@ -36,7 +51,7 @@ public enum ShellCheckTestItem {
shellProcessControl.restart();
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), "f2.txt");
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
try (var c = shellProcessControl
.subShell(shellProcessControl.getShellType())
.command(shellProcessControl.getShellType().getStreamFileWriteCommand(fileTwo))
@ -59,11 +74,13 @@ public enum ShellCheckTestItem {
SIMPLE_WRITE(shellProcessControl -> {
var content = "hello worldß";
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), "f1.txt");
shellProcessControl.executeSimpleCommand(shellProcessControl.getShellType().getSimpleFileWriteCommand(content, fileOne));
var fileOne = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
shellProcessControl.executeSimpleCommand(
shellProcessControl.getShellType().getTextFileWriteCommand(content, fileOne));
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), "f2.txt");
shellProcessControl.executeSimpleCommand(shellProcessControl.getShellType().getSimpleFileWriteCommand(content, fileTwo));
var fileTwo = FileNames.join(shellProcessControl.getOsType().getTempDirectory(shellProcessControl), UUID.randomUUID().toString());
shellProcessControl.executeSimpleCommand(
shellProcessControl.getShellType().getTextFileWriteCommand(content, fileTwo));
var s1 = shellProcessControl.executeStringSimpleCommand(
shellProcessControl.getShellType().getFileReadCommand(fileOne));
@ -74,12 +91,12 @@ public enum ShellCheckTestItem {
}),
TERMINAL_OPEN(shellProcessControl -> {
shellProcessControl.prepareTerminalOpen(null);
shellProcessControl.prepareIntermediateTerminalOpen(null);
}),
COMMAND_TERMINAL_OPEN(shellProcessControl -> {
for (CommandCheckTestItem v : CommandCheckTestItem.values()) {
shellProcessControl.prepareTerminalOpen(v.getCommandFunction().apply(shellProcessControl));
shellProcessControl.prepareIntermediateTerminalOpen(v.getCommandFunction().apply(shellProcessControl));
}
}),

View file

@ -39,6 +39,10 @@ public interface DataStoreProvider {
throw new ExtensionException("Provider " + getId() + " has no set category");
}
default DataStore getParent(DataStore store) {
return null;
}
default GuiDialog guiDialog(Property<DataStore> store) {
return null;
}

View file

@ -72,6 +72,10 @@ public class TrackEvent {
return builder().type("debug").message(message);
}
public static TrackEventBuilder withDebug(String cat, String message) {
return builder().category(cat).type("debug").message(message);
}
public static void debug(String cat, String message) {
builder().category(cat).type("debug").message(message).build().handle();
}

View file

@ -1,6 +1,8 @@
package io.xpipe.extension.fxcomps.util;
import javafx.beans.binding.Binding;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@ -8,6 +10,7 @@ import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
public class BindingsHelper {
@ -44,6 +47,45 @@ public class BindingsHelper {
});
}
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().map(map).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
return l1;
}
public static <V> ObservableList<V> orderedContentBinding(ObservableList<V> l2, Comparator<V> comp) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().sorted(comp).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
return l1;
}
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().filter(predicate.getValue()).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
predicate.addListener((c,o,n) -> {
runnable.run();
});
return l1;
}
public static <T> void setContent(ObservableList<T> toSet, List<? extends T> newList) {
if (toSet.equals(newList)) {
return;

View file

@ -40,8 +40,8 @@ public class DataStoreFormatter {
return func.apply(length);
}
var fileString = func.apply(length - atString.length() - 4);
return String.format("%s -> %s", atString, fileString);
var fileString = func.apply(length - atString.length() - 3);
return String.format("%s > %s", atString, fileString);
}
public static String toName(DataStore input) {
@ -96,6 +96,17 @@ public class DataStoreFormatter {
);
}
if (input.endsWith(".compute.amazonaws.com")) {
var split = input.split("\\.");
var name = split[0];
var region = split[1];
var lengthShare = (length - 3) / 2;
return String.format(
"%s.%s",
DataStoreFormatter.cut(name, lengthShare), DataStoreFormatter.cut(region, length - lengthShare)
);
}
return cut(input, length);
}
}

View file

@ -1,21 +1,24 @@
package io.xpipe.extension.util;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellType;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.SecretValue;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.core.util.XPipeTempDirectory;
import io.xpipe.extension.event.TrackEvent;
import lombok.SneakyThrows;
import java.util.Objects;
import java.util.List;
import java.util.Random;
public class ScriptHelper {
public static int getConnectionHash(String command) {
return Math.abs(Objects.hash(command, XPipeSession.get().getSystemSessionId()));
// This deterministic approach can cause permission problems when two different users execute the same command on a system
return new Random().nextInt(Integer.MAX_VALUE);
//return Math.abs(Objects.hash(command, XPipeSession.get().getSystemSessionId()));
}
@SneakyThrows
@ -25,6 +28,38 @@ public class ScriptHelper {
}
}
public static String constructOpenWithInitScriptCommand(ShellProcessControl processControl, List<String> init, String toExecuteInShell) {
ShellType t = processControl.getShellType();
if (init.size() == 0 && toExecuteInShell == null) {
return t.getNormalOpenCommand();
}
String nl = t.getNewLine().getNewLineString();
var content = String.join(nl, init)
+ nl;
if (processControl.getOsType().equals(OsType.LINUX)
|| processControl.getOsType().equals(OsType.MAC)) {
content = "if [ -f ~/.bashrc ]; then . ~/.bashrc; fi\n" + content;
}
if (toExecuteInShell != null) {
content += toExecuteInShell + nl;
content += t.getExitCommand() + nl;
}
var initFile = createExecScript(processControl, content, true);
return t.getInitFileOpenCommand(initFile);
}
public static String prepend(ShellProcessControl processControl, List<String> init, String commands) {
var prefix = init != null && init.size() > 0
? String.join(processControl.getShellType().getNewLine().getNewLineString(), init)
+ processControl.getShellType().getNewLine().getNewLineString()
: "";
return prefix + commands;
}
@SneakyThrows
public static String createExecScript(ShellProcessControl processControl, String content, boolean restart) {
var fileName = "exec-" + getConnectionHash(content);
@ -35,7 +70,8 @@ public class ScriptHelper {
}
@SneakyThrows
private static String createExecScript(ShellProcessControl processControl, String file, String content, boolean restart) {
private static String createExecScript(
ShellProcessControl processControl, String file, String content, boolean restart) {
ShellType type = processControl.getShellType();
content = type.prepareScriptContent(content);
@ -49,32 +85,19 @@ public class ScriptHelper {
.handle();
processControl.executeSimpleCommand(type.getFileTouchCommand(file), "Failed to create script " + file);
processControl.executeSimpleCommand(type.getMakeExecutableCommand(file), "Failed to make script " + file + " executable");
if (!content.contains("\n")) {
processControl.executeSimpleCommand(type.getSimpleFileWriteCommand(content, file));
return file;
}
try (var c = processControl.command(type.getStreamFileWriteCommand(file)).start()) {
c.discardOut();
c.discardErr();
c.getStdin().write(content.getBytes(processControl.getCharset()));
c.closeStdin();
}
if (restart) {
processControl.restart();
}
processControl.executeSimpleCommand(
type.getMakeExecutableCommand(file), "Failed to make script " + file + " executable");
processControl.executeSimpleCommand(type.getTextFileWriteCommand(content, file));
return file;
}
@SneakyThrows
public static String createAskPassScript(SecretValue pass, ShellProcessControl parent, ShellType type, boolean restart) {
public static String createAskPassScript(
SecretValue pass, ShellProcessControl parent, ShellType type, boolean restart) {
var content = type.getScriptEchoCommand(pass.getSecretValue());
var temp = XPipeTempDirectory.get(parent);
var file = FileNames.join(temp, "askpass-" + getConnectionHash(content) + "." + type.getScriptFileEnding());
return createExecScript(parent,file, content, restart);
return createExecScript(parent, file, content, restart);
}
}