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
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);

View file

@ -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();
}
}

View file

@ -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<FileSystem.FileEntry> 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);
});
});
}

View file

@ -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"));

View file

@ -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) {

View file

@ -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();
}

View file

@ -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 {