mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +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 -> {
|
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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class EditStoreAction implements ActionProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMajor() {
|
public boolean isMajor() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue