diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java index ed8afc1d2..761eebb6e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java @@ -73,7 +73,7 @@ public class BrowserComp extends SimpleComp { } model.getOpenFileSystems().addListener((ListChangeListener) c -> { - PlatformThread.runLaterBlocking(() -> { + PlatformThread.runLaterIfNeededBlocking(() -> { while (c.next()) { for (var r : c.getRemoved()) { var t = map.remove(r); diff --git a/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java b/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java index e5707e1c6..764bc3b6c 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java @@ -2,26 +2,51 @@ package io.xpipe.app.browser; +import io.xpipe.app.issue.ErrorEvent; 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.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; +import org.apache.commons.io.FilenameUtils; + +import java.util.List; 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 String path; - private final boolean directory; + private final FileSystem.FileEntry entry; private final Property editing; - public FileContextMenu(OpenFileSystemModel model, String path, boolean directory, Property editing) { + public FileContextMenu(OpenFileSystemModel model, FileSystem.FileEntry entry, Property editing) { super(); this.model = model; - this.path = path; - this.directory = directory; + this.entry = entry; this.editing = editing; createMenu(); } @@ -30,14 +55,14 @@ final class FileContextMenu extends ContextMenu { var cut = new MenuItem("Delete"); cut.setOnAction(event -> { event.consume(); - model.deleteAsync(path); + model.deleteAsync(entry.getPath()); }); cut.setAccelerator(new KeyCodeCombination(KeyCode.DELETE)); var rename = new MenuItem("Rename"); rename.setOnAction(event -> { event.consume(); - editing.setValue(path); + editing.setValue(entry.getPath()); }); rename.setAccelerator(new KeyCodeCombination(KeyCode.F2)); @@ -47,20 +72,50 @@ final class FileContextMenu extends ContextMenu { rename ); - if (directory) { + if (entry.isDirectory()) { var terminal = new MenuItem("Terminal"); terminal.setOnAction(event -> { event.consume(); - model.openTerminalAsync(path); + model.openTerminalAsync(entry.getPath()); }); getItems().add(0, terminal); } else { - var open = new MenuItem("Edit"); + var open = new MenuItem("Open"); open.setOnAction(event -> { event.consume(); - ExternalEditor.get().openInEditor(model.getFileSystem(), path); + ExternalEditor.get().openInEditor(model.getFileSystem(), entry.getPath()); }); 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); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/FileListComp.java b/app/src/main/java/io/xpipe/app/browser/FileListComp.java index 558e54653..fcc7e63fa 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListComp.java @@ -8,6 +8,7 @@ import io.xpipe.app.comp.base.LazyTextFieldComp; import io.xpipe.app.core.AppResources; import io.xpipe.app.fxcomps.impl.PrettyImageComp; 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.HumanReadableFormat; import io.xpipe.core.impl.FileNames; @@ -47,7 +48,9 @@ final class FileListComp extends AnchorPane { public FileListComp(FileListModel fileList) { this.fileList = fileList; TableView table = createTable(); - fileList.getComparatorProperty().bind(table.comparatorProperty()); + SimpleChangeListener.apply(table.comparatorProperty(), (newValue) -> { + fileList.setComparator(newValue); + }); getChildren().setAll(table); getStyleClass().addAll("table-directory-view"); @@ -118,8 +121,7 @@ final class FileListComp extends AnchorPane { var cm = new FileContextMenu( fileList.getModel(), - row.getItem().getPath(), - row.getItem().isDirectory(), + row.getItem(), editing); if (t.getButton() == MouseButton.SECONDARY) { cm.show(row, t.getScreenX(), t.getScreenY()); @@ -231,7 +233,10 @@ final class FileListComp extends AnchorPane { return row; }); - BindingsHelper.bindContent(table.getItems(), fileList.getShown()); + + fileList.getShown().addListener((observable, oldValue, newValue) -> { + BindingsHelper.setContent(table.getItems(), newValue); + }); return table; } diff --git a/app/src/main/java/io/xpipe/app/browser/FileListModel.java b/app/src/main/java/io/xpipe/app/browser/FileListModel.java index 40d130979..ffccc0e1b 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListModel.java @@ -6,17 +6,14 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ExternalEditor; import io.xpipe.core.impl.FileNames; import io.xpipe.core.store.FileSystem; -import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; 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 java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.function.Predicate; @Getter @@ -30,28 +27,33 @@ final class FileListModel { private final OpenFileSystemModel model; private final Property> comparatorProperty = new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR); - private final ObservableList all = FXCollections.observableArrayList(); - private final ObservableList shown; + private final Property> all = new SimpleObjectProperty<>(List.of()); + private final Property> shown = new SimpleObjectProperty<>(List.of()); private final ObjectProperty> predicateProperty = new SimpleObjectProperty<>(path -> true); public FileListModel(OpenFileSystemModel model) { this.model = model; - var filteredList = new FilteredList<>(all); - filteredList.predicateProperty().bind(predicateProperty); + } - var sortedList = new SortedList<>(filteredList); - sortedList - .comparatorProperty() - .bind(Bindings.createObjectBinding( - () -> { - Comparator tableComparator = comparatorProperty.getValue(); - return tableComparator != null - ? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) - : FILE_TYPE_COMPARATOR; - }, - comparatorProperty)); - shown = sortedList; + public void setAll(List newFiles) { + all.setValue(newFiles); + refreshShown(); + } + + public void setComparator(Comparator comparator) { + comparatorProperty.setValue(comparator); + refreshShown(); + } + + private void refreshShown() { + Comparator 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) { diff --git a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java index f203fcde0..51f1efb9f 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java @@ -2,6 +2,7 @@ package io.xpipe.app.browser; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.impl.FileNames; +import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.ShellStore; @@ -14,10 +15,32 @@ public class FileSystemHelper { 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 { if (local == null) { var model = new OpenFileSystemModel(); - model.switchSync(ShellStore.local()); + model.switchFileSystem(ShellStore.local()); local = model; } diff --git a/app/src/main/java/io/xpipe/app/browser/NavigationHistory.java b/app/src/main/java/io/xpipe/app/browser/NavigationHistory.java index d05df91d2..3764e934a 100644 --- a/app/src/main/java/io/xpipe/app/browser/NavigationHistory.java +++ b/app/src/main/java/io/xpipe/app/browser/NavigationHistory.java @@ -25,14 +25,19 @@ final class NavigationHistory { return history.size() > 0 ? history.get(cursor.get()) : null; } - public void append(String s) { + public void cd(String s) { if (s == null) { return; } - var lastString = history.size() > 0 ? history.get(history.size() - 1) : null; - if (!Objects.equals(lastString, s)) { - history.add(s); + var lastString = getCurrent(); + if (Objects.equals(lastString, s)) { + return; } + + if (canGoForth.get()) { + history.subList(cursor.get() + 1, history.size()).clear(); + } + history.add(s); cursor.set(history.size() - 1); } diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index ddf1613e9..a6b0ba2b3 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -2,7 +2,6 @@ package io.xpipe.app.browser; -import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.TerminalHelper; @@ -17,15 +16,15 @@ import lombok.Getter; import java.io.IOException; import java.nio.file.Path; import java.time.Instant; +import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.stream.Collectors; @Getter final class OpenFileSystemModel { private Property store = new SimpleObjectProperty<>(); private FileSystem fileSystem; - private List roots; private final FileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); private final NavigationHistory history = new NavigationHistory(); @@ -51,35 +50,35 @@ final class OpenFileSystemModel { public void cd(String path) { ThreadHelper.runFailableAsync(() -> { - cdSync(path); + try (var ignored = new BusyProperty(busy)) { + cdSync(path); + } }); } private boolean cdSync(String path) { - try (var ignored = new BusyProperty(busy)) { - if (!navigateTo(path)) { - return false; - } + path = FileSystemHelper.normalizeDirectoryPath(this, path); - currentPath.set(path); - if (!Objects.equals(history.getCurrent(), path)) { - history.append(path); - } - return true; + if (!navigateToSync(path)) { + return false; } + + currentPath.set(path); + history.cd(path); + return true; } - private boolean navigateTo(String dir) { + private boolean navigateToSync(String dir) { try { List newList; if (dir != null) { - newList = getFileSystem().listFiles(dir).toList(); + newList = getFileSystem().listFiles(dir).collect(Collectors.toCollection(ArrayList::new)); } else { newList = getFileSystem().listRoots().stream() .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; } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); @@ -96,7 +95,8 @@ final class OpenFileSystemModel { }); } - public void dropFilesIntoAsync(FileSystem.FileEntry target, List files, boolean explicitCopy) { + public void dropFilesIntoAsync( + FileSystem.FileEntry target, List files, boolean explicitCopy) { ThreadHelper.runFailableAsync(() -> { BusyProperty.execute(busy, () -> { FileSystemHelper.dropFilesInto(target, files, explicitCopy); @@ -154,26 +154,30 @@ final class OpenFileSystemModel { store = null; } - public void switchSync(FileSystemStore fileSystem) throws Exception { + public void switchFileSystem(FileSystemStore fileSystem) throws Exception { BusyProperty.execute(busy, () -> { - 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); + switchSync(fileSystem); }); } + 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) { ThreadHelper.runFailableAsync(() -> { switchSync(fileSystem); diff --git a/app/src/main/java/io/xpipe/app/browser/Utils.java b/app/src/main/java/io/xpipe/app/browser/Utils.java deleted file mode 100644 index 7463b6af6..000000000 --- a/app/src/main/java/io/xpipe/app/browser/Utils.java +++ /dev/null @@ -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; - } - } -} diff --git a/app/src/main/java/io/xpipe/app/comp/source/GuiDsStoreSelectStep.java b/app/src/main/java/io/xpipe/app/comp/source/GuiDsStoreSelectStep.java index 021f1af0c..09939dc8b 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/GuiDsStoreSelectStep.java +++ b/app/src/main/java/io/xpipe/app/comp/source/GuiDsStoreSelectStep.java @@ -108,13 +108,13 @@ public class GuiDsStoreSelectStep extends MultiStepComp.Step { + PlatformThread.runLaterIfNeededBlocking(() -> { baseSource.setValue(ds.asNeeded()); parent.next(); }); } catch (Exception e) { ErrorEvent.fromThrowable(e).build().handle(); - PlatformThread.runLaterBlocking(() -> { + PlatformThread.runLaterIfNeededBlocking(() -> { baseSource.setValue(null); }); } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java b/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java index 7f1f13276..415752ecb 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java @@ -273,7 +273,7 @@ public class PlatformThread { } } - public static void runLaterBlocking(Runnable r) { + public static void runLaterIfNeededBlocking(Runnable r) { if (!Platform.isFxApplicationThread()) { CountDownLatch latch = new CountDownLatch(1); Platform.runLater(() -> { @@ -288,4 +288,16 @@ public class PlatformThread { 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) { + } + } } diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java index 34bdee657..a366007ed 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java @@ -37,7 +37,10 @@ public class ErrorHandlerComp extends SimpleComp { } 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) { if (!showing.get()) { showing.set(true); diff --git a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java b/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java index 313c64063..64403a533 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java @@ -15,7 +15,7 @@ public class UpdateChangelogAlert { public static void showIfNeeded() { 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(); return; } diff --git a/app/src/main/java/io/xpipe/app/util/BusyProperty.java b/app/src/main/java/io/xpipe/app/util/BusyProperty.java index f1e94989f..581b4bc51 100644 --- a/app/src/main/java/io/xpipe/app/util/BusyProperty.java +++ b/app/src/main/java/io/xpipe/app/util/BusyProperty.java @@ -15,6 +15,10 @@ public class BusyProperty implements AutoCloseable { public BusyProperty(BooleanProperty prop) { this.prop = prop; + + while (prop.get()) { + ThreadHelper.sleep(50); + } prop.setValue(true); } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css index 49f597b78..b5fbb4931 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css @@ -1,7 +1,7 @@ .bar { -fx-padding: 0.8em 1.0em 0.8em 1.0em; --fx-background-color: -color-neutral-muted; --fx-border-color: -color-neutral-emphasis; +-fx-background-color: -color-neutral-subtle; +-fx-border-color: -color-border-default; } .store-header-bar { diff --git a/core/src/main/java/io/xpipe/core/impl/FileNames.java b/core/src/main/java/io/xpipe/core/impl/FileNames.java index 9c943fe45..a27e74498 100644 --- a/core/src/main/java/io/xpipe/core/impl/FileNames.java +++ b/core/src/main/java/io/xpipe/core/impl/FileNames.java @@ -5,6 +5,18 @@ import java.util.List; 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) { var split = file.split("[\\\\/]"); if (split.length == 0) { diff --git a/core/src/main/java/io/xpipe/core/impl/LocalStore.java b/core/src/main/java/io/xpipe/core/impl/LocalStore.java index 2c85aa030..66fed896b 100644 --- a/core/src/main/java/io/xpipe/core/impl/LocalStore.java +++ b/core/src/main/java/io/xpipe/core/impl/LocalStore.java @@ -12,6 +12,7 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; @JsonTypeName("local") @@ -26,6 +27,11 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac public FileSystem createFileSystem() { if (true) return new ConnectionFileSystem(ShellStore.local().create()); return new FileSystem() { + @Override + public Optional getShell() { + return Optional.empty(); + } + @Override public FileSystem open() throws Exception { return this; diff --git a/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java b/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java index d6c8ce2e5..e56f3379d 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java @@ -5,7 +5,6 @@ import lombok.SneakyThrows; import java.io.*; import java.nio.charset.Charset; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; public interface CommandProcessControl extends ProcessControl { @@ -22,47 +21,29 @@ public interface CommandProcessControl extends ProcessControl { ShellProcessControl getParent(); default InputStream startExternalStdout() throws Exception { - try { - start(); - - AtomicReference err = new AtomicReference<>(""); - accumulateStderr(s -> err.set(s)); - - return new FilterInputStream(getStdout()) { - @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; - } + start(); + discardErr(); + return new FilterInputStream(getStdout()) { + @Override + @SneakyThrows + public void close() throws IOException { + CommandProcessControl.this.close(); + } + }; } default OutputStream startExternalStdin() throws Exception { - try { - start(); - discardOut(); - discardErr(); - return new FilterOutputStream(getStdin()) { - @Override - @SneakyThrows - public void close() throws IOException { - closeStdin(); - CommandProcessControl.this.close(); - CommandProcessControl.this.getParent().restart(); - } - }; - } catch (Exception ex) { - close(); - throw ex; - } + start(); + discardOut(); + discardErr(); + return new FilterOutputStream(getStdin()) { + @Override + @SneakyThrows + public void close() throws IOException { + closeStdin(); + CommandProcessControl.this.close(); + } + }; } public boolean waitFor(); diff --git a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java index 6cee4c40a..48c9b1b1f 100644 --- a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java +++ b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java @@ -1,24 +1,28 @@ package io.xpipe.core.process; +import lombok.Getter; + +@Getter 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); - } - - 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); + this.exitCode = exitCode; + this.output = output; } } diff --git a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java index 3f7d97c13..d2048b49d 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellProcessControl.java @@ -7,11 +7,14 @@ import lombok.NonNull; import java.io.IOException; import java.util.List; +import java.util.concurrent.Semaphore; import java.util.function.Consumer; import java.util.function.Predicate; public interface ShellProcessControl extends ProcessControl { + Semaphore getCommandLock(); + void onInit(Consumer pc); String prepareTerminalOpen() throws Exception; @@ -44,8 +47,7 @@ public interface ShellProcessControl extends ProcessControl { try (CommandProcessControl c = command(command).start()) { c.discardOrThrow(); } catch (ProcessOutputException out) { - var message = out.getMessage(); - throw new ProcessOutputException(message != null ? failMessage + ": " + message : failMessage); + throw ProcessOutputException.of(failMessage, out); } } diff --git a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java index 546445c58..463b48849 100644 --- a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; @Getter @@ -33,6 +34,11 @@ public class ConnectionFileSystem implements FileSystem { return shellProcessControl.getShellDialect().listFiles(this, shellProcessControl, file); } + @Override + public Optional getShell() { + return Optional.of(shellProcessControl); + } + @Override public FileSystem open() throws Exception { shellProcessControl.start(); diff --git a/core/src/main/java/io/xpipe/core/store/FileSystem.java b/core/src/main/java/io/xpipe/core/store/FileSystem.java index 370dff011..4a84385a3 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -1,5 +1,6 @@ package io.xpipe.core.store; +import io.xpipe.core.process.ShellProcessControl; import lombok.NonNull; import lombok.Value; @@ -8,6 +9,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; public interface FileSystem extends Closeable, AutoCloseable { @@ -25,6 +27,8 @@ public interface FileSystem extends Closeable, AutoCloseable { long size; } + Optional getShell(); + FileSystem open() throws Exception; InputStream openInput(String file) throws Exception; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java index 6b11d61fe..394516d3a 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java @@ -33,7 +33,7 @@ public class EditStoreAction implements ActionProvider { @Override public boolean isMajor() { - return true; + return false; } @Override