From 305f8d69e8464e849f91959b7ba55de6197e54ca Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 22 Feb 2023 14:14:01 +0000 Subject: [PATCH] More browser fixes --- .../io/xpipe/app/browser/BrowserModel.java | 18 +--- .../io/xpipe/app/browser/FileContextMenu.java | 37 ++++---- .../io/xpipe/app/browser/FileListComp.java | 7 +- .../io/xpipe/app/browser/FileListModel.java | 4 +- .../xpipe/app/browser/FileSystemHelper.java | 1 + .../app/browser/OpenFileSystemModel.java | 8 +- .../app/comp/about/BrowseDirectoryComp.java | 5 +- .../app/comp/base/IntegratedTextAreaComp.java | 6 +- .../java/io/xpipe/app/core/mode/BaseMode.java | 4 +- .../{ExternalEditor.java => FileBridge.java} | 53 ++++-------- .../java/io/xpipe/app/util/FileOpener.java | 84 +++++++++++++++++++ .../java/io/xpipe/app/util/ScriptHelper.java | 10 +++ .../java/io/xpipe/core/store/FileSystem.java | 1 + .../ext/base/actions/FileEditAction.java | 12 +-- 14 files changed, 150 insertions(+), 100 deletions(-) rename app/src/main/java/io/xpipe/app/util/{ExternalEditor.java => FileBridge.java} (83%) create mode 100644 app/src/main/java/io/xpipe/app/util/FileOpener.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java index 852791b7e..5257715f0 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java @@ -1,8 +1,6 @@ package io.xpipe.app.browser; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.ShellStore; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; @@ -18,20 +16,6 @@ public class BrowserModel { private final ObservableList openFileSystems = FXCollections.observableArrayList(); private final Property selected = new SimpleObjectProperty<>(); - public OpenFileSystemModel getOpenModelFor(DataStore store) { - return openFileSystems.stream() - .filter(model -> model.getStore().equals(store)) - .findFirst() - .orElseThrow(); - } - - public OpenFileSystemModel getOpenModelFor(FileSystem fileSystem) { - return openFileSystems.stream() - .filter(model -> model.getFileSystem().equals(fileSystem)) - .findFirst() - .orElseThrow(); - } - public void closeFileSystem(OpenFileSystemModel open) { ThreadHelper.runAsync(() -> { open.closeSync(); @@ -41,7 +25,7 @@ public class BrowserModel { public void openFileSystem(ShellStore store) { var found = openFileSystems.stream() - .filter(fileSystemModel -> fileSystemModel.getStore().equals(store)) + .filter(fileSystemModel -> fileSystemModel.getStore().getValue().equals(store)) .findFirst(); if (found.isPresent()) { selected.setValue(found.get()); 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 2ba7624a8..05085220f 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/FileContextMenu.java @@ -2,10 +2,7 @@ package io.xpipe.app.browser; -import io.xpipe.app.util.ExternalEditor; -import io.xpipe.app.util.ScriptHelper; -import io.xpipe.app.util.TerminalHelper; -import io.xpipe.app.util.ThreadHelper; +import io.xpipe.app.util.*; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.store.FileSystem; @@ -21,11 +18,15 @@ import java.util.List; final class FileContextMenu extends ContextMenu { - public boolean isScript(FileSystem.FileEntry e) { + public boolean isExecutable(FileSystem.FileEntry e) { if (e.isDirectory()) { return false; } + if (e.getExecutable() != null && e.getExecutable()) { + return true; + } + var shell = e.getFileSystem().getShell(); if (shell.isEmpty()) { return false; @@ -33,7 +34,7 @@ final class FileContextMenu extends ContextMenu { var os = shell.get().getOsType(); var ending = FilenameUtils.getExtension(e.getPath()).toLowerCase(); - if (os.equals(OsType.WINDOWS) && List.of("bat", "ps1", "cmd").contains(ending)) { + if (os.equals(OsType.WINDOWS) && List.of("exe", "bat", "ps1", "cmd").contains(ending)) { return true; } @@ -65,7 +66,7 @@ final class FileContextMenu extends ContextMenu { }); getItems().add(terminal); } else { - if (isScript(entry)) { + if (isExecutable(entry)) { var execute = new MenuItem("Run in terminal"); execute.setOnAction(event -> { ThreadHelper.runFailableAsync(() -> { @@ -91,23 +92,21 @@ final class FileContextMenu extends ContextMenu { event.consume(); }); getItems().add(executeInBackground); - } - - var open = new MenuItem("Open default"); - open.setOnAction(event -> { - ThreadHelper.runFailableAsync(() -> { - ShellProcessControl pc = model.getFileSystem().getShell().orElseThrow(); - var cmd = "\"" + entry.getPath() + "\""; - pc.executeBooleanSimpleCommand(cmd); + } else { + var open = new MenuItem("Open default"); + open.setOnAction(event -> { + ThreadHelper.runFailableAsync(() -> { + FileOpener.openInDefaultApplication(entry); + }); + event.consume(); }); - event.consume(); - }); - getItems().add(open); + getItems().add(open); + } var edit = new MenuItem("Edit"); edit.setOnAction(event -> { + FileOpener.openInTextEditor(entry); event.consume(); - ExternalEditor.get().openInEditor(model.getFileSystem(), entry.getPath()); }); getItems().add(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 fcc7e63fa..a625f6590 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListComp.java @@ -7,7 +7,6 @@ import atlantafx.base.theme.Tweaks; 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; @@ -235,7 +234,11 @@ final class FileListComp extends AnchorPane { }); fileList.getShown().addListener((observable, oldValue, newValue) -> { - BindingsHelper.setContent(table.getItems(), newValue); + table.getItems().setAll(newValue); + + if (newValue.size() > 0) { + table.scrollTo(0); + } }); 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 ffccc0e1b..5dd8e4899 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileListModel.java @@ -3,7 +3,7 @@ package io.xpipe.app.browser; import io.xpipe.app.issue.ErrorEvent; -import io.xpipe.app.util.ExternalEditor; +import io.xpipe.app.util.FileOpener; import io.xpipe.core.impl.FileNames; import io.xpipe.core.store.FileSystem; import javafx.beans.property.ObjectProperty; @@ -73,7 +73,7 @@ final class FileListModel { if (entry.isDirectory()) { model.navigate(entry.getPath(), true); } else { - ExternalEditor.get().openInEditor(entry.getFileSystem(), entry.getPath()); + FileOpener.openInTextEditor(entry); } } 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 51f1efb9f..608288868 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java @@ -54,6 +54,7 @@ public class FileSystemHelper { Files.getLastModifiedTime(file).toInstant(), Files.isDirectory(file), Files.isHidden(file), + Files.isExecutable(file), Files.size(file)); } 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 a6b0ba2b3..c232c1535 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -45,7 +45,7 @@ final class OpenFileSystemModel { } public FileSystem.FileEntry getCurrentDirectory() { - return new FileSystem.FileEntry(fileSystem, currentPath.get(), Instant.now(), true, false, 0); + return new FileSystem.FileEntry(fileSystem, currentPath.get(), Instant.now(), true, false, false, 0); } public void cd(String path) { @@ -75,7 +75,7 @@ final class OpenFileSystemModel { 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)) + .map(s -> new FileSystem.FileEntry(getFileSystem(), s, Instant.now(), true, false, false, 0)) .collect(Collectors.toCollection(ArrayList::new)); } fileList.setAll(newList); @@ -180,7 +180,9 @@ final class OpenFileSystemModel { public void switchAsync(FileSystemStore fileSystem) { ThreadHelper.runFailableAsync(() -> { - switchSync(fileSystem); + BusyProperty.execute(busy, () -> { + switchSync(fileSystem); + }); }); } diff --git a/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java b/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java index 13299a3d9..1d0f74bbe 100644 --- a/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/about/BrowseDirectoryComp.java @@ -8,7 +8,7 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.UserReportComp; import io.xpipe.app.util.DesktopHelper; import io.xpipe.app.util.DynamicOptionsBuilder; -import io.xpipe.app.util.ExternalEditor; +import io.xpipe.app.util.FileOpener; import io.xpipe.core.util.XPipeInstallation; import javafx.scene.layout.Region; @@ -30,8 +30,7 @@ public class BrowseDirectoryComp extends SimpleComp { .addComp( "logFile", new ButtonComp(AppI18n.observable("openCurrentLogFile"), () -> { - ExternalEditor.get() - .openInEditor(AppLogs.get() + FileOpener.openInTextEditor(AppLogs.get() .getSessionLogsDirectory() .resolve("xpipe.log") .toString()); diff --git a/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java b/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java index 421150959..85e35732a 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java @@ -4,7 +4,7 @@ import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.IconButtonComp; import io.xpipe.app.fxcomps.impl.TextAreaComp; -import io.xpipe.app.util.ExternalEditor; +import io.xpipe.app.util.FileOpener; import javafx.application.Platform; import javafx.beans.property.Property; import javafx.scene.layout.AnchorPane; @@ -47,8 +47,8 @@ public class IntegratedTextAreaComp extends SimpleComp { } private Region createOpenButton(Region container) { - var button = new IconButtonComp("mdal-edit", () -> ExternalEditor.get() - .startEditing(identifier, fileType, this, value.getValue(), (s) -> { + var button = new IconButtonComp("mdal-edit", () -> FileOpener + .openString(identifier, fileType, this, value.getValue(), (s) -> { Platform.runLater(() -> value.setValue(s)); })) .createRegion(); diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index 52e150d42..9d0a4d627 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -9,7 +9,7 @@ import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.update.AppUpdater; -import io.xpipe.app.util.ExternalEditor; +import io.xpipe.app.util.FileBridge; import io.xpipe.core.util.JacksonMapper; public class BaseMode extends OperationMode { @@ -40,7 +40,7 @@ public class BaseMode extends OperationMode { AppCharsetter.init(); DataStorage.init(); FileWatchManager.init(); - ExternalEditor.init(); + FileBridge.init(); AppSocketServer.init(); AppUpdater.init(); TrackEvent.info("mode", "Finished base components initialization"); diff --git a/app/src/main/java/io/xpipe/app/util/ExternalEditor.java b/app/src/main/java/io/xpipe/app/util/FileBridge.java similarity index 83% rename from app/src/main/java/io/xpipe/app/util/ExternalEditor.java rename to app/src/main/java/io/xpipe/app/util/FileBridge.java index c01997e1a..698eb1407 100644 --- a/app/src/main/java/io/xpipe/app/util/ExternalEditor.java +++ b/app/src/main/java/io/xpipe/app/util/FileBridge.java @@ -4,11 +4,8 @@ import io.xpipe.app.core.FileWatchManager; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.prefs.AppPrefs; -import io.xpipe.core.impl.FileNames; -import io.xpipe.core.store.FileSystem; import lombok.Getter; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.function.FailableSupplier; import java.io.*; @@ -24,14 +21,14 @@ import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; -public class ExternalEditor { +public class FileBridge { private static final Path TEMP = - FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("editor"); - private static ExternalEditor INSTANCE; + FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("bridge"); + private static FileBridge INSTANCE; private final Set openEntries = new CopyOnWriteArraySet<>(); - public static ExternalEditor get() { + public static FileBridge get() { return INSTANCE; } @@ -44,7 +41,7 @@ public class ExternalEditor { } public static void init() { - INSTANCE = new ExternalEditor(); + INSTANCE = new FileBridge(); try { FileUtils.forceMkdir(TEMP.toFile()); @@ -124,13 +121,13 @@ public class ExternalEditor { return Optional.empty(); } - public void startEditing(String keyName, String fileType, Object key, String input, Consumer output) { + public void openString(String keyName, String fileType, Object key, String input, Consumer output, Consumer consumer) { if (input == null) { input = ""; } String s = input; - startEditing( + openIO( keyName, fileType, key, @@ -141,18 +138,20 @@ public class ExternalEditor { super.close(); output.accept(new String(toByteArray(), StandardCharsets.UTF_8)); } - }); + }, + consumer); } - public void startEditing( + public void openIO( String keyName, String fileType, Object key, FailableSupplier input, - FailableSupplier output) { + FailableSupplier output, + Consumer consumer) { var ext = getForKey(key); if (ext.isPresent()) { - openInEditor(ext.get().file.toString()); + consumer.accept(ext.get().file.toString()); return; } @@ -181,31 +180,7 @@ public class ExternalEditor { openEntries.add(entry); ext = getForKey(key); - openInEditor(ext.orElseThrow().file.toString()); - } - - public void openInEditor(FileSystem fileSystem, String file) { - var editor = AppPrefs.get().externalEditor().getValue(); - if (editor == null || !editor.isSelectable()) { - return; - } - - startEditing(FileNames.getFileName(file), FilenameUtils.getExtension(file), file, () -> { - return fileSystem.openInput(file); - }, () -> fileSystem.openOutput(file)); - } - - public void openInEditor(String file) { - var editor = AppPrefs.get().externalEditor().getValue(); - if (editor == null || !editor.isSelectable()) { - return; - } - - try { - editor.launch(Path.of(file).toRealPath()); - } catch (Exception e) { - ErrorEvent.fromThrowable(e).handle(); - } + consumer.accept(ext.orElseThrow().file.toString()); } @Getter diff --git a/app/src/main/java/io/xpipe/app/util/FileOpener.java b/app/src/main/java/io/xpipe/app/util/FileOpener.java new file mode 100644 index 000000000..179f4ccfd --- /dev/null +++ b/app/src/main/java/io/xpipe/app/util/FileOpener.java @@ -0,0 +1,84 @@ +package io.xpipe.app.util; + +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.core.impl.FileNames; +import io.xpipe.core.process.OsType; +import io.xpipe.core.store.FileSystem; +import io.xpipe.core.store.ShellStore; +import org.apache.commons.io.FilenameUtils; + +import java.nio.file.Path; +import java.util.function.Consumer; + +public class FileOpener { + + public static void openInDefaultApplication(FileSystem.FileEntry entry) { + var editor = AppPrefs.get().externalEditor().getValue(); + if (editor == null || !editor.isSelectable()) { + return; + } + + var file = entry.getPath(); + FileBridge.get() + .openIO( + FileNames.getFileName(file), + FilenameUtils.getExtension(file), + file, + () -> { + return entry.getFileSystem().openInput(file); + }, + () -> entry.getFileSystem().openOutput(file), + s -> openInDefaultApplication(s)); + } + + public static void openInTextEditor(FileSystem.FileEntry entry) { + var editor = AppPrefs.get().externalEditor().getValue(); + if (editor == null || !editor.isSelectable()) { + return; + } + + var file = entry.getPath(); + FileBridge.get() + .openIO( + FileNames.getFileName(file), + FilenameUtils.getExtension(file), + file, + () -> { + return entry.getFileSystem().openInput(file); + }, + () -> entry.getFileSystem().openOutput(file), + FileOpener::openInTextEditor); + } + + public static void openInTextEditor(String file) { + var editor = AppPrefs.get().externalEditor().getValue(); + if (editor == null) { + return; + } + + try { + editor.launch(Path.of(file).toRealPath()); + } catch (Exception e) { + ErrorEvent.fromThrowable(e).handle(); + } + } + + public static void openInDefaultApplication(String file) { + try (var pc = ShellStore.local().create().start()) { + if (pc.getOsType().equals(OsType.WINDOWS)) { + pc.executeSimpleCommand("\"" + file + "\""); + } else if (pc.getOsType().equals(OsType.LINUX)) { + pc.executeSimpleCommand("xdg-open \"" + file + "\""); + } else { + pc.executeSimpleCommand("open \"" + file + "\""); + } + } catch (Exception e) { + ErrorEvent.fromThrowable(e).handle(); + } + } + + public static void openString(String keyName, String fileType, Object key, String input, Consumer output) { + FileBridge.get().openString(keyName, fileType, key, input, output, file -> openInTextEditor(file)); + } +} diff --git a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java index d079061a0..76d4cba15 100644 --- a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java +++ b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java @@ -15,6 +15,16 @@ import java.util.Random; public class ScriptHelper { + public static String createDefaultOpenCommand(ShellProcessControl pc, String file) { + if (pc.getOsType().equals(OsType.WINDOWS)) { + return "\"" + file + "\""; + } else if (pc.getOsType().equals(OsType.LINUX)){ + return "xdg-open \"" + file + "\""; + } else { + return "open \"" + file + "\""; + } + } + public static String createDetachCommand(ShellProcessControl pc, String command) { if (pc.getOsType().equals(OsType.WINDOWS)) { return "start \"\" " + command; 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 4a84385a3..6347e714a 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -24,6 +24,7 @@ public interface FileSystem extends Closeable, AutoCloseable { Instant date; boolean directory; boolean hidden; + Boolean executable; long size; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/actions/FileEditAction.java b/ext/base/src/main/java/io/xpipe/ext/base/actions/FileEditAction.java index b554c5b72..f287d53ee 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/actions/FileEditAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/actions/FileEditAction.java @@ -2,7 +2,7 @@ package io.xpipe.ext.base.actions; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.ActionProvider; -import io.xpipe.app.util.ExternalEditor; +import io.xpipe.app.util.FileOpener; import io.xpipe.core.impl.FileStore; import io.xpipe.core.impl.LocalStore; import io.xpipe.core.store.DataFlow; @@ -24,15 +24,7 @@ public class FileEditAction implements ActionProvider { @Override public void execute() throws Exception { if (store.getFileSystem().equals(new LocalStore())) { - ExternalEditor.get().openInEditor(store.getFile()); - } else { - ExternalEditor.get() - .startEditing( - store.getFileName(), - store.getFileExtension(), - store, - () -> store.openInput(), - () -> store.openOutput()); + FileOpener.openInTextEditor(store.getFile()); } } }