From e2308366fb0038414fde5522dd4869abb5c0b9a6 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 31 Mar 2023 13:32:12 +0000 Subject: [PATCH] Allow for the creation of connection desktop shortcuts for all distributions --- .../io/xpipe/app/browser/FileBrowserComp.java | 8 +- .../app/browser/LocalFileTransferComp.java | 127 ++++++++++-------- .../app/browser/LocalFileTransferStage.java | 35 +++-- .../io/xpipe/app/util/DesktopShortcuts.java | 16 ++- .../xpipe/app/util/HumanReadableFormat.java | 8 +- .../xpipe/app/util/XPipeDistributionType.java | 19 --- .../io/xpipe/core/util/XPipeInstallation.java | 2 +- 7 files changed, 122 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java index c70c7bbe..526f4e2b 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java @@ -38,7 +38,13 @@ public class FileBrowserComp extends SimpleComp { @Override protected Region createSimple() { var bookmarksList = new BookmarkList(model).createRegion(); - var localDownloadStage = new LocalFileTransferComp(model.getLocalTransfersStage()).createRegion(); + var localDownloadStage = new LocalFileTransferComp(model.getLocalTransfersStage()).hide(Bindings.createBooleanBinding(() -> { + if (model.getOpenFileSystems().size() == 0) { + return true; + } + + return !model.getMode().equals(FileBrowserModel.Mode.BROWSER); + }, PlatformThread.sync(model.getOpenFileSystems()))).createRegion(); var vertical = new VBox(bookmarksList, localDownloadStage); vertical.setFillWidth(true); diff --git a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java index 27839fec..60d25b6f 100644 --- a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferComp.java @@ -2,7 +2,9 @@ package io.xpipe.app.browser; import io.xpipe.app.comp.base.LoadingOverlayComp; import io.xpipe.app.core.AppI18n; +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.LabelComp; import io.xpipe.app.fxcomps.impl.StackComp; import io.xpipe.app.fxcomps.impl.VerticalComp; @@ -17,6 +19,7 @@ import javafx.scene.image.WritableImage; import javafx.scene.input.ClipboardContent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Region; import org.kordamp.ikonli.javafx.FontIcon; @@ -38,67 +41,85 @@ public class LocalFileTransferComp extends SimpleComp { .visible(BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))); var backgroundStack = new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); + var binding = BindingsHelper.mappedContentBinding(stage.getItems(), item -> item.getFileEntry()); - var list = new SelectedFileListComp(binding).apply(struc -> struc.get().setMinHeight(200)); + var list = new SelectedFileListComp(binding).apply(struc -> struc.get().setMinHeight(200)).grow(false, true); var dragNotice = new LabelComp(AppI18n.observable("dragFiles")) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export"))) .hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))) .grow(true, false) .apply(struc -> struc.get().setPadding(new Insets(8))); - var loading = new LoadingOverlayComp( - new VerticalComp(List.of(list, dragNotice)), PlatformThread.sync(stage.getDownloading())); - var stack = new StackComp(List.of(backgroundStack, loading)).apply(struc -> { - struc.get().setOnDragOver(event -> { - // Accept drops from inside the app window - if (event.getGestureSource() != null) { - event.acceptTransferModes(TransferMode.ANY); - event.consume(); - } - }); - struc.get().setOnDragDropped(event -> { - if (event.getGestureSource() != null) { - var files = FileBrowserClipboard.retrieveDrag(event.getDragboard()) - .getEntries(); - stage.drop(files); - event.setDropCompleted(true); - event.consume(); - } - }); - struc.get().setOnDragDetected(event -> { - if (stage.getDownloading().get()) { - return; - } - var files = stage.getItems().stream() - .map(item -> { - try { - return item.getLocalFile().toRealPath().toFile(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .toList(); - Dragboard db = struc.get().startDragAndDrop(TransferMode.MOVE); - var cc = new ClipboardContent(); - cc.putFiles(files); - db.setContent(cc); - - var r = new SelectedFileListComp(FXCollections.observableList(stage.getItems().stream() - .map(item -> item.getFileEntry()) - .toList())) - .createRegion(); - new Scene(r); - WritableImage image = r.snapshot(new SnapshotParameters(), null); - db.setDragView(image, -20, 15); - - event.setDragDetect(true); - event.consume(); - }); - struc.get().setOnDragDone(event -> { - stage.getItems().clear(); - event.consume(); - }); + var clearButton = new IconButtonComp("mdi2d-delete", () -> { + stage.getItems().clear(); + }) + .hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))); + var clearPane = Comp.derive(clearButton, button -> { + var p = new AnchorPane(button); + AnchorPane.setRightAnchor(button, 10.0); + AnchorPane.setTopAnchor(button, 10.0); + return p; }); + + var listBox = new VerticalComp(List.of(list, dragNotice)); + var stack = new LoadingOverlayComp( + new StackComp(List.of(backgroundStack, listBox, clearPane)).apply(struc -> { + struc.get().setOnDragOver(event -> { + // Accept drops from inside the app window + if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) { + event.acceptTransferModes(TransferMode.ANY); + event.consume(); + } + }); + struc.get().setOnDragDropped(event -> { + if (event.getGestureSource() != null) { + var files = FileBrowserClipboard.retrieveDrag(event.getDragboard()) + .getEntries(); + stage.drop(files); + event.setDropCompleted(true); + event.consume(); + } + }); + struc.get().setOnDragDetected(event -> { + if (stage.getDownloading().get()) { + return; + } + + var files = stage.getItems().stream() + .map(item -> { + try { + return item.getLocalFile().toRealPath().toFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .toList(); + Dragboard db = struc.get().startDragAndDrop(TransferMode.MOVE); + var cc = new ClipboardContent(); + cc.putFiles(files); + db.setContent(cc); + + var r = new SelectedFileListComp(FXCollections.observableList(stage.getItems().stream() + .map(item -> item.getFileEntry()) + .toList())) + .createRegion(); + new Scene(r); + WritableImage image = r.snapshot(new SnapshotParameters(), null); + db.setDragView(image, -20, 15); + + event.setDragDetect(true); + event.consume(); + }); + struc.get().setOnDragDone(event -> { + if (!event.isAccepted()) { + return; + } + + stage.getItems().clear(); + event.consume(); + }); + }), + PlatformThread.sync(stage.getDownloading())); return stack.createRegion(); } } diff --git a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferStage.java b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferStage.java index c6e33b34..699781ca 100644 --- a/app/src/main/java/io/xpipe/app/browser/LocalFileTransferStage.java +++ b/app/src/main/java/io/xpipe/app/browser/LocalFileTransferStage.java @@ -1,7 +1,7 @@ package io.xpipe.app.browser; +import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.BusyProperty; -import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.impl.FileNames; import io.xpipe.core.store.FileSystem; import javafx.beans.property.BooleanProperty; @@ -13,6 +13,8 @@ import org.apache.commons.io.FileUtils; import java.nio.file.Path; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Value public class LocalFileTransferStage { @@ -20,8 +22,16 @@ public class LocalFileTransferStage { private static final Path TEMP = FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("download"); + ExecutorService executor = Executors.newSingleThreadExecutor(r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + t.setName("file downloader"); + return t; + }); + @Value public static class Item { + String name; FileSystem.FileEntry fileEntry; Path localFile; BooleanProperty finishedDownload = new SimpleBooleanProperty(); @@ -32,15 +42,24 @@ public class LocalFileTransferStage { public void drop(List entries) { entries.forEach(entry -> { - Path file = TEMP.resolve(FileNames.getFileName(entry.getPath())); - var item = new Item(entry, file); + var name = FileNames.getFileName(entry.getPath()); + if (items.stream().anyMatch(item -> item.getName().equals(name))) { + return; + } + + Path file = TEMP.resolve(name); + var item = new Item(name, entry, file); items.add(item); - ThreadHelper.runFailableAsync(() -> { - FileUtils.forceMkdirParent(TEMP.toFile()); - try (var b = new BusyProperty(downloading)) { - FileSystemHelper.dropFilesInto(FileSystemHelper.getLocal(TEMP),List.of(entry), false); + executor.submit(() -> { + try { + FileUtils.forceMkdirParent(TEMP.toFile()); + try (var b = new BusyProperty(downloading)) { + FileSystemHelper.dropFilesInto(FileSystemHelper.getLocal(TEMP), List.of(entry), false); + } + item.finishedDownload.set(true); + } catch (Throwable t) { + ErrorEvent.fromThrowable(t).handle(); } - item.finishedDownload.set(true); }); }); } diff --git a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java index 905d635d..be60b05d 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java @@ -11,19 +11,21 @@ public class DesktopShortcuts { private static void createWindowsShortcut(String target, String name) throws Exception { var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); + var shortcutTarget = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.WINDOWS)); var content = String.format( """ set "TARGET=%s" set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk" set PWS=powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile - %%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.TargetPath = '%%TARGET%%'; $S.Save()" + %%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.TargetPath = '%%TARGET%%'; $S.Arguments = '%s'; $S.Save()" """, - target, name, icon.toString()); + shortcutTarget, name, icon.toString(), target); LocalStore.getShell().executeSimpleCommand(content); } private static void createLinuxShortcut(String target, String name) throws Exception { + var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.LINUX)); var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var content = String.format( """ @@ -31,27 +33,27 @@ public class DesktopShortcuts { Type=Application Name=%s Comment=Open with X-Pipe - TryExec=/opt/xpipe/app/bin/xpiped - Exec=/opt/xpipe/cli/bin/xpipe open %s + Exec="%s" open %s Icon=%s Terminal=false Categories=Utility;Development;Office; """, - name, target, icon.toString()); + name, exec, target, icon.toString()); var file = Path.of(System.getProperty("user.home") + "/Desktop/" + name + ".desktop"); Files.writeString(file, content); file.toFile().setExecutable(true); } private static void createMacOSShortcut(String target, String name) throws Exception { + var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.MACOS)); var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var base = System.getProperty("user.home") + "/Desktop/" + name + ".app"; var content = String.format( """ #!/bin/bash - open %s + "%s" %s """, - target); + exec, target); try (var pc = LocalStore.getShell()) { pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS")); diff --git a/app/src/main/java/io/xpipe/app/util/HumanReadableFormat.java b/app/src/main/java/io/xpipe/app/util/HumanReadableFormat.java index 733a678c..77fc8922 100644 --- a/app/src/main/java/io/xpipe/app/util/HumanReadableFormat.java +++ b/app/src/main/java/io/xpipe/app/util/HumanReadableFormat.java @@ -19,15 +19,15 @@ public final class HumanReadableFormat { public static final DateTimeFormatter HOUR_MINUTE = DateTimeFormatter.ofPattern("HH:mm"); public static String byteCount(long bytes) { - if (-1000 < bytes && bytes < 1000) { + if (-1024 < bytes && bytes < 1024) { return bytes + " B"; } CharacterIterator ci = new StringCharacterIterator("kMGTPE"); - while (bytes <= -999_950 || bytes >= 999_950) { - bytes /= 1000; + while (bytes <= -1024 * 1024 || bytes >= 1024 * 1024) { + bytes /= 1024; ci.next(); } - return String.format("%.1f %cB", bytes / 1000.0, ci.current()); + return String.format("%.1f %cB", bytes / 1024.0, ci.current()); } public static String date(LocalDateTime x) { diff --git a/app/src/main/java/io/xpipe/app/util/XPipeDistributionType.java b/app/src/main/java/io/xpipe/app/util/XPipeDistributionType.java index 616662d5..9c47b462 100644 --- a/app/src/main/java/io/xpipe/app/util/XPipeDistributionType.java +++ b/app/src/main/java/io/xpipe/app/util/XPipeDistributionType.java @@ -1,7 +1,6 @@ package io.xpipe.app.util; import io.xpipe.app.issue.TrackEvent; -import io.xpipe.core.process.OsType; import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.XPipeInstallation; @@ -19,12 +18,6 @@ public interface XPipeDistributionType { TrackEvent.info("Development mode update executed"); } - @Override - public boolean supportsURLs() { - // Enabled for testing - return true; - } - @Override public String getName() { return "development"; @@ -40,11 +33,6 @@ public interface XPipeDistributionType { @Override public void performUpdateAction() {} - @Override - public boolean supportsURLs() { - return OsType.getLocal().equals(OsType.MACOS); - } - @Override public String getName() { return "portable"; @@ -62,11 +50,6 @@ public interface XPipeDistributionType { TrackEvent.info("Update action called"); } - @Override - public boolean supportsURLs() { - return true; - } - @Override public String getName() { return "install"; @@ -89,7 +72,5 @@ public interface XPipeDistributionType { void performUpdateAction(); - boolean supportsURLs(); - String getName(); } diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index d6d4a31f..cbcccee3 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -272,7 +272,7 @@ public class XPipeInstallation { public static String getRelativeCliExecutablePath(OsType type) { if (type.equals(OsType.WINDOWS)) { - return FileNames.join("cli", "xpipe.exe"); + return FileNames.join("cli", "bin", "xpipe.exe"); } else if (type.equals(OsType.LINUX)) { return FileNames.join("cli", "bin", "xpipe"); } else {