Allow for the creation of connection desktop shortcuts for all distributions

This commit is contained in:
crschnick 2023-03-31 13:32:12 +00:00
parent 2763ca40c8
commit e2308366fb
7 changed files with 122 additions and 93 deletions

View file

@ -38,7 +38,13 @@ public class FileBrowserComp extends SimpleComp {
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var bookmarksList = new BookmarkList(model).createRegion(); 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); var vertical = new VBox(bookmarksList, localDownloadStage);
vertical.setFillWidth(true); vertical.setFillWidth(true);

View file

@ -2,7 +2,9 @@ package io.xpipe.app.browser;
import io.xpipe.app.comp.base.LoadingOverlayComp; import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; 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.LabelComp;
import io.xpipe.app.fxcomps.impl.StackComp; import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; 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.ClipboardContent;
import javafx.scene.input.Dragboard; import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
@ -38,67 +41,85 @@ public class LocalFileTransferComp extends SimpleComp {
.visible(BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))); .visible(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())));
var backgroundStack = var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = BindingsHelper.mappedContentBinding(stage.getItems(), item -> item.getFileEntry()); 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")) var dragNotice = new LabelComp(AppI18n.observable("dragFiles"))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export"))) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export")))
.hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems()))) .hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())))
.grow(true, false) .grow(true, false)
.apply(struc -> struc.get().setPadding(new Insets(8))); .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() var clearButton = new IconButtonComp("mdi2d-delete", () -> {
.map(item -> { stage.getItems().clear();
try { })
return item.getLocalFile().toRealPath().toFile(); .hide(BindingsHelper.persist(Bindings.isEmpty(stage.getItems())));
} catch (IOException e) { var clearPane = Comp.derive(clearButton, button -> {
throw new RuntimeException(e); var p = new AnchorPane(button);
} AnchorPane.setRightAnchor(button, 10.0);
}) AnchorPane.setTopAnchor(button, 10.0);
.toList(); return p;
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 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(); return stack.createRegion();
} }
} }

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser; package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
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.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -13,6 +13,8 @@ import org.apache.commons.io.FileUtils;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Value @Value
public class LocalFileTransferStage { public class LocalFileTransferStage {
@ -20,8 +22,16 @@ public class LocalFileTransferStage {
private static final Path TEMP = private static final Path TEMP =
FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("download"); 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 @Value
public static class Item { public static class Item {
String name;
FileSystem.FileEntry fileEntry; FileSystem.FileEntry fileEntry;
Path localFile; Path localFile;
BooleanProperty finishedDownload = new SimpleBooleanProperty(); BooleanProperty finishedDownload = new SimpleBooleanProperty();
@ -32,15 +42,24 @@ public class LocalFileTransferStage {
public void drop(List<FileSystem.FileEntry> entries) { public void drop(List<FileSystem.FileEntry> entries) {
entries.forEach(entry -> { entries.forEach(entry -> {
Path file = TEMP.resolve(FileNames.getFileName(entry.getPath())); var name = FileNames.getFileName(entry.getPath());
var item = new Item(entry, file); 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); items.add(item);
ThreadHelper.runFailableAsync(() -> { executor.submit(() -> {
FileUtils.forceMkdirParent(TEMP.toFile()); try {
try (var b = new BusyProperty(downloading)) { FileUtils.forceMkdirParent(TEMP.toFile());
FileSystemHelper.dropFilesInto(FileSystemHelper.getLocal(TEMP),List.of(entry), false); 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);
}); });
}); });
} }

View file

@ -11,19 +11,21 @@ public class DesktopShortcuts {
private static void createWindowsShortcut(String target, String name) throws Exception { private static void createWindowsShortcut(String target, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var shortcutTarget = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.WINDOWS));
var content = String.format( var content = String.format(
""" """
set "TARGET=%s" set "TARGET=%s"
set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk" set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk"
set PWS=powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile 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); LocalStore.getShell().executeSimpleCommand(content);
} }
private static void createLinuxShortcut(String target, String name) throws Exception { private static void createLinuxShortcut(String target, String name) throws Exception {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.LINUX));
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var content = String.format( var content = String.format(
""" """
@ -31,27 +33,27 @@ public class DesktopShortcuts {
Type=Application Type=Application
Name=%s Name=%s
Comment=Open with X-Pipe Comment=Open with X-Pipe
TryExec=/opt/xpipe/app/bin/xpiped Exec="%s" open %s
Exec=/opt/xpipe/cli/bin/xpipe open %s
Icon=%s Icon=%s
Terminal=false Terminal=false
Categories=Utility;Development;Office; Categories=Utility;Development;Office;
""", """,
name, target, icon.toString()); name, exec, target, icon.toString());
var file = Path.of(System.getProperty("user.home") + "/Desktop/" + name + ".desktop"); var file = Path.of(System.getProperty("user.home") + "/Desktop/" + name + ".desktop");
Files.writeString(file, content); Files.writeString(file, content);
file.toFile().setExecutable(true); file.toFile().setExecutable(true);
} }
private static void createMacOSShortcut(String target, String name) throws Exception { private static void createMacOSShortcut(String target, String name) throws Exception {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.MACOS));
var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var base = System.getProperty("user.home") + "/Desktop/" + name + ".app"; var base = System.getProperty("user.home") + "/Desktop/" + name + ".app";
var content = String.format( var content = String.format(
""" """
#!/bin/bash #!/bin/bash
open %s "%s" %s
""", """,
target); exec, target);
try (var pc = LocalStore.getShell()) { try (var pc = LocalStore.getShell()) {
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS")); pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS"));

View file

@ -19,15 +19,15 @@ public final class HumanReadableFormat {
public static final DateTimeFormatter HOUR_MINUTE = DateTimeFormatter.ofPattern("HH:mm"); public static final DateTimeFormatter HOUR_MINUTE = DateTimeFormatter.ofPattern("HH:mm");
public static String byteCount(long bytes) { public static String byteCount(long bytes) {
if (-1000 < bytes && bytes < 1000) { if (-1024 < bytes && bytes < 1024) {
return bytes + " B"; return bytes + " B";
} }
CharacterIterator ci = new StringCharacterIterator("kMGTPE"); CharacterIterator ci = new StringCharacterIterator("kMGTPE");
while (bytes <= -999_950 || bytes >= 999_950) { while (bytes <= -1024 * 1024 || bytes >= 1024 * 1024) {
bytes /= 1000; bytes /= 1024;
ci.next(); 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) { public static String date(LocalDateTime x) {

View file

@ -1,7 +1,6 @@
package io.xpipe.app.util; package io.xpipe.app.util;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
@ -19,12 +18,6 @@ public interface XPipeDistributionType {
TrackEvent.info("Development mode update executed"); TrackEvent.info("Development mode update executed");
} }
@Override
public boolean supportsURLs() {
// Enabled for testing
return true;
}
@Override @Override
public String getName() { public String getName() {
return "development"; return "development";
@ -40,11 +33,6 @@ public interface XPipeDistributionType {
@Override @Override
public void performUpdateAction() {} public void performUpdateAction() {}
@Override
public boolean supportsURLs() {
return OsType.getLocal().equals(OsType.MACOS);
}
@Override @Override
public String getName() { public String getName() {
return "portable"; return "portable";
@ -62,11 +50,6 @@ public interface XPipeDistributionType {
TrackEvent.info("Update action called"); TrackEvent.info("Update action called");
} }
@Override
public boolean supportsURLs() {
return true;
}
@Override @Override
public String getName() { public String getName() {
return "install"; return "install";
@ -89,7 +72,5 @@ public interface XPipeDistributionType {
void performUpdateAction(); void performUpdateAction();
boolean supportsURLs();
String getName(); String getName();
} }

View file

@ -272,7 +272,7 @@ public class XPipeInstallation {
public static String getRelativeCliExecutablePath(OsType type) { public static String getRelativeCliExecutablePath(OsType type) {
if (type.equals(OsType.WINDOWS)) { if (type.equals(OsType.WINDOWS)) {
return FileNames.join("cli", "xpipe.exe"); return FileNames.join("cli", "bin", "xpipe.exe");
} else if (type.equals(OsType.LINUX)) { } else if (type.equals(OsType.LINUX)) {
return FileNames.join("cli", "bin", "xpipe"); return FileNames.join("cli", "bin", "xpipe");
} else { } else {