mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Many small fixes
This commit is contained in:
parent
27d25ca666
commit
fab1d75f45
22 changed files with 269 additions and 198 deletions
|
@ -73,7 +73,7 @@ public class BrowserComp extends SimpleComp {
|
|||
}
|
||||
|
||||
model.getOpenFileSystems().addListener((ListChangeListener<? super OpenFileSystemModel>) c -> {
|
||||
PlatformThread.runLaterBlocking(() -> {
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
while (c.next()) {
|
||||
for (var r : c.getRemoved()) {
|
||||
var t = map.remove(r);
|
||||
|
|
|
@ -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<String> editing;
|
||||
|
||||
public FileContextMenu(OpenFileSystemModel model, String path, boolean directory, Property<String> editing) {
|
||||
public FileContextMenu(OpenFileSystemModel model, FileSystem.FileEntry entry, Property<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FileSystem.FileEntry> 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;
|
||||
}
|
||||
|
|
|
@ -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<Comparator<FileSystem.FileEntry>> comparatorProperty =
|
||||
new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR);
|
||||
private final ObservableList<FileSystem.FileEntry> all = FXCollections.observableArrayList();
|
||||
private final ObservableList<FileSystem.FileEntry> shown;
|
||||
private final Property<List<FileSystem.FileEntry>> all = new SimpleObjectProperty<>(List.of());
|
||||
private final Property<List<FileSystem.FileEntry>> shown = new SimpleObjectProperty<>(List.of());
|
||||
private final ObjectProperty<Predicate<FileSystem.FileEntry>> 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<FileSystem.FileEntry> tableComparator = comparatorProperty.getValue();
|
||||
return tableComparator != null
|
||||
? FILE_TYPE_COMPARATOR.thenComparing(tableComparator)
|
||||
: FILE_TYPE_COMPARATOR;
|
||||
},
|
||||
comparatorProperty));
|
||||
shown = sortedList;
|
||||
public void setAll(List<FileSystem.FileEntry> newFiles) {
|
||||
all.setValue(newFiles);
|
||||
refreshShown();
|
||||
}
|
||||
|
||||
public void setComparator(Comparator<FileSystem.FileEntry> comparator) {
|
||||
comparatorProperty.setValue(comparator);
|
||||
refreshShown();
|
||||
}
|
||||
|
||||
private void refreshShown() {
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FileSystemStore> store = new SimpleObjectProperty<>();
|
||||
private FileSystem fileSystem;
|
||||
private List<String> roots;
|
||||
private final FileListModel fileList;
|
||||
private final ReadOnlyObjectWrapper<String> 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<FileSystem.FileEntry> 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<FileSystem.FileEntry> files, boolean explicitCopy) {
|
||||
public void dropFilesIntoAsync(
|
||||
FileSystem.FileEntry target, List<FileSystem.FileEntry> 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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -108,13 +108,13 @@ public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? ext
|
|||
return;
|
||||
}
|
||||
|
||||
PlatformThread.runLaterBlocking(() -> {
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
baseSource.setValue(ds.asNeeded());
|
||||
parent.next();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).build().handle();
|
||||
PlatformThread.runLaterBlocking(() -> {
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
baseSource.setValue(null);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ShellProcessControl> getShell() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystem open() throws Exception {
|
||||
return this;
|
||||
|
|
|
@ -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<String> 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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ShellProcessControl> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ShellProcessControl> getShell() {
|
||||
return Optional.of(shellProcessControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystem open() throws Exception {
|
||||
shellProcessControl.start();
|
||||
|
|
|
@ -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<ShellProcessControl> getShell();
|
||||
|
||||
FileSystem open() throws Exception;
|
||||
|
||||
InputStream openInput(String file) throws Exception;
|
||||
|
|
|
@ -33,7 +33,7 @@ public class EditStoreAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public boolean isMajor() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in a new issue