Many small fixes

This commit is contained in:
crschnick 2023-02-21 23:41:39 +00:00
parent 27d25ca666
commit fab1d75f45
22 changed files with 269 additions and 198 deletions

View file

@ -73,7 +73,7 @@ public class BrowserComp extends SimpleComp {
} }
model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> { model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> {
PlatformThread.runLaterBlocking(() -> { PlatformThread.runLaterIfNeededBlocking(() -> {
while (c.next()) { while (c.next()) {
for (var r : c.getRemoved()) { for (var r : c.getRemoved()) {
var t = map.remove(r); var t = map.remove(r);

View file

@ -2,26 +2,51 @@
package io.xpipe.app.browser; package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ExternalEditor; import io.xpipe.app.util.ExternalEditor;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
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;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
import org.apache.commons.io.FilenameUtils;
import java.util.List;
final class FileContextMenu extends ContextMenu { final class FileContextMenu extends ContextMenu {
public boolean isScript(FileSystem.FileEntry e) {
if (e.isDirectory()) {
return false;
}
var shell = e.getFileSystem().getShell();
if (shell.isEmpty()) {
return false;
}
var os = shell.get().getOsType();
var ending = FilenameUtils.getExtension(e.getPath()).toLowerCase();
if (os.equals(OsType.WINDOWS) && List.of("bat", "ps1", "cmd").contains(ending)) {
return true;
}
return false;
}
private final OpenFileSystemModel model; private final OpenFileSystemModel model;
private final String path; private final FileSystem.FileEntry entry;
private final boolean directory;
private final Property<String> editing; private final Property<String> editing;
public FileContextMenu(OpenFileSystemModel model, String path, boolean directory, Property<String> editing) { public FileContextMenu(OpenFileSystemModel model, FileSystem.FileEntry entry, Property<String> editing) {
super(); super();
this.model = model; this.model = model;
this.path = path; this.entry = entry;
this.directory = directory;
this.editing = editing; this.editing = editing;
createMenu(); createMenu();
} }
@ -30,14 +55,14 @@ final class FileContextMenu extends ContextMenu {
var cut = new MenuItem("Delete"); var cut = new MenuItem("Delete");
cut.setOnAction(event -> { cut.setOnAction(event -> {
event.consume(); event.consume();
model.deleteAsync(path); model.deleteAsync(entry.getPath());
}); });
cut.setAccelerator(new KeyCodeCombination(KeyCode.DELETE)); cut.setAccelerator(new KeyCodeCombination(KeyCode.DELETE));
var rename = new MenuItem("Rename"); var rename = new MenuItem("Rename");
rename.setOnAction(event -> { rename.setOnAction(event -> {
event.consume(); event.consume();
editing.setValue(path); editing.setValue(entry.getPath());
}); });
rename.setAccelerator(new KeyCodeCombination(KeyCode.F2)); rename.setAccelerator(new KeyCodeCombination(KeyCode.F2));
@ -47,20 +72,50 @@ final class FileContextMenu extends ContextMenu {
rename rename
); );
if (directory) { if (entry.isDirectory()) {
var terminal = new MenuItem("Terminal"); var terminal = new MenuItem("Terminal");
terminal.setOnAction(event -> { terminal.setOnAction(event -> {
event.consume(); event.consume();
model.openTerminalAsync(path); model.openTerminalAsync(entry.getPath());
}); });
getItems().add(0, terminal); getItems().add(0, terminal);
} else { } else {
var open = new MenuItem("Edit"); var open = new MenuItem("Open");
open.setOnAction(event -> { open.setOnAction(event -> {
event.consume(); event.consume();
ExternalEditor.get().openInEditor(model.getFileSystem(), path); ExternalEditor.get().openInEditor(model.getFileSystem(), entry.getPath());
}); });
getItems().add(0, open); getItems().add(0, open);
if (isScript(entry)) {
var executeInBackground = new MenuItem("Run in background");
executeInBackground.setOnAction(event -> {
event.consume();
ExternalEditor.get().openInEditor(model.getFileSystem(), entry.getPath());
});
getItems().add(0, executeInBackground);
var execute = new MenuItem("Run in terminal");
execute.setOnAction(event -> {
event.consume();
try {
ShellProcessControl pc = model.getFileSystem().getShell().orElseThrow();
pc.executeSimpleCommand(pc.getShellDialect().getMakeExecutableCommand(entry.getPath()));
var cmd = pc.command(entry.getPath()).prepareTerminalOpen();
TerminalHelper.open(FilenameUtils.getName(entry.getPath()), cmd);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
getItems().add(0, execute);
}
var edit = new MenuItem("Edit");
edit.setOnAction(event -> {
event.consume();
ExternalEditor.get().openInEditor(model.getFileSystem(), entry.getPath());
});
getItems().add(0, edit);
} }
} }
} }

View file

@ -8,6 +8,7 @@ import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.core.AppResources; import io.xpipe.app.core.AppResources;
import io.xpipe.app.fxcomps.impl.PrettyImageComp; import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.util.Containers; import io.xpipe.app.util.Containers;
import io.xpipe.app.util.HumanReadableFormat; import io.xpipe.app.util.HumanReadableFormat;
import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.FileNames;
@ -47,7 +48,9 @@ final class FileListComp extends AnchorPane {
public FileListComp(FileListModel fileList) { public FileListComp(FileListModel fileList) {
this.fileList = fileList; this.fileList = fileList;
TableView<FileSystem.FileEntry> table = createTable(); TableView<FileSystem.FileEntry> table = createTable();
fileList.getComparatorProperty().bind(table.comparatorProperty()); SimpleChangeListener.apply(table.comparatorProperty(), (newValue) -> {
fileList.setComparator(newValue);
});
getChildren().setAll(table); getChildren().setAll(table);
getStyleClass().addAll("table-directory-view"); getStyleClass().addAll("table-directory-view");
@ -118,8 +121,7 @@ final class FileListComp extends AnchorPane {
var cm = new FileContextMenu( var cm = new FileContextMenu(
fileList.getModel(), fileList.getModel(),
row.getItem().getPath(), row.getItem(),
row.getItem().isDirectory(),
editing); editing);
if (t.getButton() == MouseButton.SECONDARY) { if (t.getButton() == MouseButton.SECONDARY) {
cm.show(row, t.getScreenX(), t.getScreenY()); cm.show(row, t.getScreenX(), t.getScreenY());
@ -231,7 +233,10 @@ final class FileListComp extends AnchorPane {
return row; return row;
}); });
BindingsHelper.bindContent(table.getItems(), fileList.getShown());
fileList.getShown().addListener((observable, oldValue, newValue) -> {
BindingsHelper.setContent(table.getItems(), newValue);
});
return table; return table;
} }

View file

@ -6,17 +6,14 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ExternalEditor; import io.xpipe.app.util.ExternalEditor;
import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.FileNames;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import lombok.Getter; import lombok.Getter;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@Getter @Getter
@ -30,28 +27,33 @@ final class FileListModel {
private final OpenFileSystemModel model; private final OpenFileSystemModel model;
private final Property<Comparator<FileSystem.FileEntry>> comparatorProperty = private final Property<Comparator<FileSystem.FileEntry>> comparatorProperty =
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR); new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
private final ObservableList<FileSystem.FileEntry> all = FXCollections.observableArrayList(); private final Property<List<FileSystem.FileEntry>> all = new SimpleObjectProperty<>(List.of());
private final ObservableList<FileSystem.FileEntry> shown; private final Property<List<FileSystem.FileEntry>> shown = new SimpleObjectProperty<>(List.of());
private final ObjectProperty<Predicate<FileSystem.FileEntry>> predicateProperty = private final ObjectProperty<Predicate<FileSystem.FileEntry>> predicateProperty =
new SimpleObjectProperty<>(path -> true); new SimpleObjectProperty<>(path -> true);
public FileListModel(OpenFileSystemModel model) { public FileListModel(OpenFileSystemModel model) {
this.model = model; this.model = model;
var filteredList = new FilteredList<>(all); }
filteredList.predicateProperty().bind(predicateProperty);
var sortedList = new SortedList<>(filteredList); public void setAll(List<FileSystem.FileEntry> newFiles) {
sortedList all.setValue(newFiles);
.comparatorProperty() refreshShown();
.bind(Bindings.createObjectBinding( }
() -> {
Comparator<FileSystem.FileEntry> tableComparator = comparatorProperty.getValue(); public void setComparator(Comparator<FileSystem.FileEntry> comparator) {
return tableComparator != null comparatorProperty.setValue(comparator);
? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) refreshShown();
: FILE_TYPE_COMPARATOR; }
},
comparatorProperty)); private void refreshShown() {
shown = sortedList; Comparator<FileSystem.FileEntry> tableComparator = comparatorProperty.getValue();
var comparator = tableComparator != null
? FILE_TYPE_COMPARATOR.thenComparing(tableComparator)
: FILE_TYPE_COMPARATOR;
var listCopy = new ArrayList<>(all.getValue());
listCopy.sort(comparator);
shown.setValue(listCopy);
} }
public boolean rename(String filename, String newName) { public boolean rename(String filename, String newName) {

View file

@ -2,6 +2,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
@ -14,10 +15,32 @@ public class FileSystemHelper {
private static OpenFileSystemModel local; private static OpenFileSystemModel local;
public static String normalizeDirectoryPath(OpenFileSystemModel model, String path) {
if (path == null) {
return null;
}
path = path.trim();
if (path.isBlank()) {
return null;
}
var shell = model.getFileSystem().getShell();
if (shell.isEmpty()) {
return path;
}
if (shell.get().getOsType().equals(OsType.WINDOWS) && path.length() == 2 && path.endsWith(":")) {
return path + "\\";
}
return FileNames.toDirectory(path);
}
public static OpenFileSystemModel getLocal() throws Exception { public static OpenFileSystemModel getLocal() throws Exception {
if (local == null) { if (local == null) {
var model = new OpenFileSystemModel(); var model = new OpenFileSystemModel();
model.switchSync(ShellStore.local()); model.switchFileSystem(ShellStore.local());
local = model; local = model;
} }

View file

@ -25,14 +25,19 @@ final class NavigationHistory {
return history.size() > 0 ? history.get(cursor.get()) : null; return history.size() > 0 ? history.get(cursor.get()) : null;
} }
public void append(String s) { public void cd(String s) {
if (s == null) { if (s == null) {
return; return;
} }
var lastString = history.size() > 0 ? history.get(history.size() - 1) : null; var lastString = getCurrent();
if (!Objects.equals(lastString, s)) { if (Objects.equals(lastString, s)) {
history.add(s); return;
} }
if (canGoForth.get()) {
history.subList(cursor.get() + 1, history.size()).clear();
}
history.add(s);
cursor.set(history.size() - 1); cursor.set(history.size() - 1);
} }

View file

@ -2,7 +2,6 @@
package io.xpipe.app.browser; package io.xpipe.app.browser;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.TerminalHelper; import io.xpipe.app.util.TerminalHelper;
@ -17,15 +16,15 @@ import lombok.Getter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.stream.Collectors;
@Getter @Getter
final class OpenFileSystemModel { final class OpenFileSystemModel {
private Property<FileSystemStore> store = new SimpleObjectProperty<>(); private Property<FileSystemStore> store = new SimpleObjectProperty<>();
private FileSystem fileSystem; private FileSystem fileSystem;
private List<String> roots;
private final FileListModel fileList; private final FileListModel fileList;
private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<String> currentPath = new ReadOnlyObjectWrapper<>();
private final NavigationHistory history = new NavigationHistory(); private final NavigationHistory history = new NavigationHistory();
@ -51,35 +50,35 @@ final class OpenFileSystemModel {
public void cd(String path) { public void cd(String path) {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
cdSync(path); try (var ignored = new BusyProperty(busy)) {
cdSync(path);
}
}); });
} }
private boolean cdSync(String path) { private boolean cdSync(String path) {
try (var ignored = new BusyProperty(busy)) { path = FileSystemHelper.normalizeDirectoryPath(this, path);
if (!navigateTo(path)) {
return false;
}
currentPath.set(path); if (!navigateToSync(path)) {
if (!Objects.equals(history.getCurrent(), path)) { return false;
history.append(path);
}
return true;
} }
currentPath.set(path);
history.cd(path);
return true;
} }
private boolean navigateTo(String dir) { private boolean navigateToSync(String dir) {
try { try {
List<FileSystem.FileEntry> newList; List<FileSystem.FileEntry> newList;
if (dir != null) { if (dir != null) {
newList = getFileSystem().listFiles(dir).toList(); newList = getFileSystem().listFiles(dir).collect(Collectors.toCollection(ArrayList::new));
} else { } else {
newList = getFileSystem().listRoots().stream() newList = getFileSystem().listRoots().stream()
.map(s -> new FileSystem.FileEntry(getFileSystem(), s, Instant.now(), true, false, 0)) .map(s -> new FileSystem.FileEntry(getFileSystem(), s, Instant.now(), true, false, 0))
.toList(); .collect(Collectors.toCollection(ArrayList::new));
} }
BindingsHelper.setContent(fileList.getAll(), newList); fileList.setAll(newList);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).handle(); ErrorEvent.fromThrowable(e).handle();
@ -96,7 +95,8 @@ final class OpenFileSystemModel {
}); });
} }
public void dropFilesIntoAsync(FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) { public void dropFilesIntoAsync(
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
BusyProperty.execute(busy, () -> { BusyProperty.execute(busy, () -> {
FileSystemHelper.dropFilesInto(target, files, explicitCopy); FileSystemHelper.dropFilesInto(target, files, explicitCopy);
@ -154,26 +154,30 @@ final class OpenFileSystemModel {
store = null; store = null;
} }
public void switchSync(FileSystemStore fileSystem) throws Exception { public void switchFileSystem(FileSystemStore fileSystem) throws Exception {
BusyProperty.execute(busy, () -> { BusyProperty.execute(busy, () -> {
closeSync(); switchSync(fileSystem);
this.store.setValue(fileSystem);
var fs = fileSystem.createFileSystem();
fs.open();
this.fileSystem = fs;
var current = fs instanceof ConnectionFileSystem connectionFileSystem
? connectionFileSystem
.getShellProcessControl()
.executeStringSimpleCommand(connectionFileSystem
.getShellProcessControl()
.getShellDialect()
.getPrintWorkingDirectoryCommand())
: null;
cdSync(current);
}); });
} }
private void switchSync(FileSystemStore fileSystem) throws Exception {
closeSync();
this.store.setValue(fileSystem);
var fs = fileSystem.createFileSystem();
fs.open();
this.fileSystem = fs;
var current = fs instanceof ConnectionFileSystem connectionFileSystem
? connectionFileSystem
.getShellProcessControl()
.executeStringSimpleCommand(connectionFileSystem
.getShellProcessControl()
.getShellDialect()
.getPrintWorkingDirectoryCommand())
: null;
cdSync(current);
}
public void switchAsync(FileSystemStore fileSystem) { public void switchAsync(FileSystemStore fileSystem) {
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
switchSync(fileSystem); switchSync(fileSystem);

View file

@ -1,57 +0,0 @@
/* SPDX-License-Identifier: MIT */
package io.xpipe.app.browser;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
final class Utils {
private Utils() {
// Default constructor
}
public static long fileSize(Path path) {
if (path == null) {
return 0;
}
try {
return Files.size(path);
} catch (IOException e) {
return 0;
}
}
public static boolean isFileHidden(Path path) {
if (path == null) {
return false;
}
try {
return Files.isHidden(path);
} catch (IOException e) {
return false;
}
}
public static FileTime fileMTime(Path path, LinkOption... options) {
if (path == null) {
return null;
}
try {
return Files.getLastModifiedTime(path, options);
} catch (IOException e) {
return null;
}
}
public static String getMimeType(Path path) {
try {
return Files.probeContentType(path);
} catch (IOException e) {
return null;
}
}
}

View file

@ -108,13 +108,13 @@ public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? ext
return; return;
} }
PlatformThread.runLaterBlocking(() -> { PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(ds.asNeeded()); baseSource.setValue(ds.asNeeded());
parent.next(); parent.next();
}); });
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle(); ErrorEvent.fromThrowable(e).build().handle();
PlatformThread.runLaterBlocking(() -> { PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(null); baseSource.setValue(null);
}); });
} }

View file

@ -273,7 +273,7 @@ public class PlatformThread {
} }
} }
public static void runLaterBlocking(Runnable r) { public static void runLaterIfNeededBlocking(Runnable r) {
if (!Platform.isFxApplicationThread()) { if (!Platform.isFxApplicationThread()) {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> { Platform.runLater(() -> {
@ -288,4 +288,16 @@ public class PlatformThread {
r.run(); r.run();
} }
} }
public static void alwaysRunLaterBlocking(Runnable r) {
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
r.run();
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException ignored) {
}
}
} }

View file

@ -37,7 +37,10 @@ public class ErrorHandlerComp extends SimpleComp {
} }
public static void showAndWait(ErrorEvent event) { public static void showAndWait(ErrorEvent event) {
PlatformThread.runLaterBlocking(() -> { // Always run later to prevent any issues when an exception
// is thrown within an animation or layout processing task
// Otherwise, the show and wait method might fail
PlatformThread.alwaysRunLaterBlocking(() -> {
synchronized (showing) { synchronized (showing) {
if (!showing.get()) { if (!showing.get()) {
showing.set(true); showing.set(true);

View file

@ -15,7 +15,7 @@ public class UpdateChangelogAlert {
public static void showIfNeeded() { public static void showIfNeeded() {
var update = AppUpdater.get().getPerformedUpdate(); var update = AppUpdater.get().getPerformedUpdate();
if (update != null && !update.getNewVersion().equals(AppProperties.get().getVersion())) { if (update != null && !AppProperties.get().getVersion().equals(update.getNewVersion())) {
ErrorEvent.fromMessage("Update did not succeed").handle(); ErrorEvent.fromMessage("Update did not succeed").handle();
return; return;
} }

View file

@ -15,6 +15,10 @@ public class BusyProperty implements AutoCloseable {
public BusyProperty(BooleanProperty prop) { public BusyProperty(BooleanProperty prop) {
this.prop = prop; this.prop = prop;
while (prop.get()) {
ThreadHelper.sleep(50);
}
prop.setValue(true); prop.setValue(true);
} }

View file

@ -1,7 +1,7 @@
.bar { .bar {
-fx-padding: 0.8em 1.0em 0.8em 1.0em; -fx-padding: 0.8em 1.0em 0.8em 1.0em;
-fx-background-color: -color-neutral-muted; -fx-background-color: -color-neutral-subtle;
-fx-border-color: -color-neutral-emphasis; -fx-border-color: -color-border-default;
} }
.store-header-bar { .store-header-bar {

View file

@ -5,6 +5,18 @@ import java.util.List;
public class FileNames { public class FileNames {
public static String toDirectory(String path) {
if (path.endsWith("/") || path.endsWith("\\")) {
return path;
}
if (path.contains("\\")) {
return path + "\\";
}
return path + "/";
}
public static String getFileName(String file) { public static String getFileName(String file) {
var split = file.split("[\\\\/]"); var split = file.split("[\\\\/]");
if (split.length == 0) { if (split.length == 0) {

View file

@ -12,6 +12,7 @@ import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
@JsonTypeName("local") @JsonTypeName("local")
@ -26,6 +27,11 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
public FileSystem createFileSystem() { public FileSystem createFileSystem() {
if (true) return new ConnectionFileSystem(ShellStore.local().create()); if (true) return new ConnectionFileSystem(ShellStore.local().create());
return new FileSystem() { return new FileSystem() {
@Override
public Optional<ShellProcessControl> getShell() {
return Optional.empty();
}
@Override @Override
public FileSystem open() throws Exception { public FileSystem open() throws Exception {
return this; return this;

View file

@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface CommandProcessControl extends ProcessControl { public interface CommandProcessControl extends ProcessControl {
@ -22,47 +21,29 @@ public interface CommandProcessControl extends ProcessControl {
ShellProcessControl getParent(); ShellProcessControl getParent();
default InputStream startExternalStdout() throws Exception { default InputStream startExternalStdout() throws Exception {
try { start();
start(); discardErr();
return new FilterInputStream(getStdout()) {
AtomicReference<String> err = new AtomicReference<>(""); @Override
accumulateStderr(s -> err.set(s)); @SneakyThrows
public void close() throws IOException {
return new FilterInputStream(getStdout()) { CommandProcessControl.this.close();
@Override }
@SneakyThrows };
public void close() throws IOException {
CommandProcessControl.this.close();
if (!err.get().isEmpty()) {
throw new IOException(err.get());
}
CommandProcessControl.this.getParent().restart();
}
};
} catch (Exception ex) {
close();
throw ex;
}
} }
default OutputStream startExternalStdin() throws Exception { default OutputStream startExternalStdin() throws Exception {
try { start();
start(); discardOut();
discardOut(); discardErr();
discardErr(); return new FilterOutputStream(getStdin()) {
return new FilterOutputStream(getStdin()) { @Override
@Override @SneakyThrows
@SneakyThrows public void close() throws IOException {
public void close() throws IOException { closeStdin();
closeStdin(); CommandProcessControl.this.close();
CommandProcessControl.this.close(); }
CommandProcessControl.this.getParent().restart(); };
}
};
} catch (Exception ex) {
close();
throw ex;
}
} }
public boolean waitFor(); public boolean waitFor();

View file

@ -1,24 +1,28 @@
package io.xpipe.core.process; package io.xpipe.core.process;
import lombok.Getter;
@Getter
public class ProcessOutputException extends Exception { public class ProcessOutputException extends Exception {
public ProcessOutputException() {
super(); public static ProcessOutputException of(String customPrefix, ProcessOutputException ex) {
var messageSuffix = ex.getOutput() != null && ! ex.getOutput().isBlank()?": " + ex.getOutput() : "";
var message = customPrefix + messageSuffix;
return new ProcessOutputException(message, ex.getExitCode(), ex.getOutput());
} }
public ProcessOutputException(String message) { public static ProcessOutputException of(int exitCode, String output) {
var messageSuffix = output != null && !output.isBlank()?": " + output : "";
var message = exitCode == -1 ? "Process timed out" + messageSuffix : "Process returned with exit code " + exitCode + messageSuffix;
return new ProcessOutputException(message, exitCode, output);
}
private final int exitCode;
private final String output;
private ProcessOutputException(String message, int exitCode, String output) {
super(message); super(message);
} this.exitCode = exitCode;
this.output = output;
public ProcessOutputException(String message, Throwable cause) {
super(message, cause);
}
public ProcessOutputException(Throwable cause) {
super(cause);
}
protected ProcessOutputException(
String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
} }
} }

View file

@ -7,11 +7,14 @@ import lombok.NonNull;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
public interface ShellProcessControl extends ProcessControl { public interface ShellProcessControl extends ProcessControl {
Semaphore getCommandLock();
void onInit(Consumer<ShellProcessControl> pc); void onInit(Consumer<ShellProcessControl> pc);
String prepareTerminalOpen() throws Exception; String prepareTerminalOpen() throws Exception;
@ -44,8 +47,7 @@ public interface ShellProcessControl extends ProcessControl {
try (CommandProcessControl c = command(command).start()) { try (CommandProcessControl c = command(command).start()) {
c.discardOrThrow(); c.discardOrThrow();
} catch (ProcessOutputException out) { } catch (ProcessOutputException out) {
var message = out.getMessage(); throw ProcessOutputException.of(failMessage, out);
throw new ProcessOutputException(message != null ? failMessage + ": " + message : failMessage);
} }
} }

View file

@ -8,6 +8,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
@Getter @Getter
@ -33,6 +34,11 @@ public class ConnectionFileSystem implements FileSystem {
return shellProcessControl.getShellDialect().listFiles(this, shellProcessControl, file); return shellProcessControl.getShellDialect().listFiles(this, shellProcessControl, file);
} }
@Override
public Optional<ShellProcessControl> getShell() {
return Optional.of(shellProcessControl);
}
@Override @Override
public FileSystem open() throws Exception { public FileSystem open() throws Exception {
shellProcessControl.start(); shellProcessControl.start();

View file

@ -1,5 +1,6 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.process.ShellProcessControl;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;
@ -8,6 +9,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface FileSystem extends Closeable, AutoCloseable { public interface FileSystem extends Closeable, AutoCloseable {
@ -25,6 +27,8 @@ public interface FileSystem extends Closeable, AutoCloseable {
long size; long size;
} }
Optional<ShellProcessControl> getShell();
FileSystem open() throws Exception; FileSystem open() throws Exception;
InputStream openInput(String file) throws Exception; InputStream openInput(String file) throws Exception;

View file

@ -33,7 +33,7 @@ public class EditStoreAction implements ActionProvider {
@Override @Override
public boolean isMajor() { public boolean isMajor() {
return true; return false;
} }
@Override @Override