diff --git a/app/src/main/java/io/xpipe/app/beacon/api/FsReadExchange.java b/app/src/main/java/io/xpipe/app/beacon/api/FsReadExchange.java index 4503387a8..4328fcd84 100644 --- a/app/src/main/java/io/xpipe/app/beacon/api/FsReadExchange.java +++ b/app/src/main/java/io/xpipe/app/beacon/api/FsReadExchange.java @@ -44,18 +44,21 @@ public class FsReadExchange extends BeaconInterface { var size = fs.getFileSize(msg.getPath()); if (size > 100_000_000) { var file = BlobManager.get().newBlobFile(); - try (var in = fs.openInput(msg.getPath())) { - var fixedIn = new FixedSizeInputStream(new BufferedInputStream(in), size); - try (var fileOut = Files.newOutputStream(file)) { - fixedIn.transferTo(fileOut); + try { + try (var in = fs.openInput(msg.getPath())) { + var fixedIn = new FixedSizeInputStream(new BufferedInputStream(in), size); + try (var fileOut = Files.newOutputStream(file)) { + fixedIn.transferTo(fileOut); + } + in.transferTo(OutputStream.nullOutputStream()); } - in.transferTo(OutputStream.nullOutputStream()); - } - exchange.sendResponseHeaders(200, size); - try (var fileIn = Files.newInputStream(file); - var out = exchange.getResponseBody()) { - fileIn.transferTo(out); + exchange.sendResponseHeaders(200, size); + try (var fileIn = Files.newInputStream(file); var out = exchange.getResponseBody()) { + fileIn.transferTo(out); + } + } finally { + Files.deleteIfExists(file); } } else { byte[] bytes; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java index a7eed34c4..d07c6dc44 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java @@ -85,7 +85,7 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder { if (keyEvent.getCode().isFunctionKey()) { var start = KeyCode.F1.getCode(); var index = keyEvent.getCode().getCode() - start; - if (index < tabs.getTabs().size()) { + if (index < tabs.getTabs().size() && !keyEvent.isShortcutDown() && !keyEvent.isAltDown() && !keyEvent.isShiftDown()) { tabs.getSelectionModel().select(index); keyEvent.consume(); return; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 35e5e3837..9cc2d4b24 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -242,10 +242,10 @@ public final class BrowserFileListComp extends SimpleRegionBuilder { : v != null ? v.getGroupFile().getGroups().getOrDefault(unix.getGid(), "?") : null; var uid = unix.getUid() != null ? String.valueOf(unix.getUid()) - : v != null ? v.getPasswdFile().getUidForUser(user) : null; + : v != null ? v.getPasswdFile().getUidForUserIfPresent(user).orElse(null) : null; var gid = unix.getGid() != null ? String.valueOf(unix.getGid()) - : v != null ? v.getGroupFile().getGidForGroup(group) : null; + : v != null ? v.getGroupFile().getGidForGroupIfPresent(group).orElse(null) : null; var userFormat = user + (uid != null ? " [" + uid + "]" : ""); var groupFormat = group + (gid != null ? " [" + gid + "]" : ""); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java index 5b23beba8..d880e6ff1 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java @@ -121,8 +121,9 @@ public final class BrowserFileListModel { var newFullPath = fileSystemModel.getCurrentPath().get().join(newName); // This check will fail on case-insensitive file systems when changing the case of the file - // So skip it in this case - var skipExistCheck = old.getFileName().equalsIgnoreCase(newName); + // So skip it in the case we only change the case and the new target does not exist + var skipExistCheck = old.getFileName().equalsIgnoreCase(newName) && all.getValue().stream() + .noneMatch(e -> e.getFileName().equals(newName)); if (!skipExistCheck) { boolean exists; try { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java index bd618e672..0bb4e3e04 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java @@ -3,6 +3,7 @@ package io.xpipe.app.browser.file; import io.xpipe.app.browser.icon.BrowserIconManager; import io.xpipe.app.comp.base.LazyTextFieldComp; import io.xpipe.app.comp.base.PrettyImageHelper; +import io.xpipe.app.fs.FileEntry; import io.xpipe.app.fs.FileKind; import io.xpipe.app.platform.InputHelper; import io.xpipe.app.platform.MenuHelper; @@ -212,23 +213,30 @@ class BrowserFileListNameCell extends TableCell { // Visibility seems to be bugged, so use opacity setOpacity(0.0); } else { - var icon = getTableRow().getItem().getIcon(); + var i = getTableRow().getItem(); + var fileEntry = i.getRawFileEntry(); + + var icon = i.getIcon(); BrowserIconManager.loadIfNecessary(icon); img.set(icon); - var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY; + var isDirectory = fileEntry.getKind() == FileKind.DIRECTORY; pseudoClassStateChanged(PseudoClass.getPseudoClass("folder"), isDirectory); - var normalName = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.LINK - ? getTableRow().getItem().getFileName() + " -> " - + getTableRow() - .getItem() - .getRawFileEntry() - .resolved() - .getPath() - : getTableRow().getItem().getFileName(); - var fileName = normalName; - var info = getTableRow().getItem().getRawFileEntry().getInfo(); + String fileName; + if (fileEntry.getKind() == FileKind.LINK) { + var target = fileEntry + .resolved() + .getPath(); + var currentPath = fileList.getFileSystemModel().getCurrentDirectory(); + var shownTarget = currentPath != null && target.startsWith(currentPath.getPath()) ? + "./" + target.relativize(currentPath.getPath()) : target; + fileName = i.getFileName() + " -> " + shownTarget; + } else { + fileName = i.getFileName(); + } + + var info = fileEntry.getInfo(); var hidden = (info != null && info.explicitlyHidden()) || fileName.startsWith("."); getTableRow().pseudoClassStateChanged(PseudoClass.getPseudoClass("hidden"), hidden); text.set(fileName); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java index d898016d9..623e5ac82 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java @@ -106,7 +106,7 @@ public class BrowserStatusBarComp extends SimpleRegionBuilder { return "..."; } - var elapsed = (p.getTotal() - p.getTransferred() / (double) p.getTotal()) * expected.toMillis(); + var elapsed = ((p.getTotal() - p.getTransferred()) / (double) p.getTotal()) * expected.toMillis(); var show = elapsed > 3000; if (!show) { return "..."; diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/compress/TarActionProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/compress/TarActionProvider.java index 94b964da5..66f5ea736 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/impl/compress/TarActionProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/compress/TarActionProvider.java @@ -44,7 +44,7 @@ public class TarActionProvider implements BrowserActionProvider { .add("|", "sed") .addLiteral("s,^" + dir.toDirectory().toUnix() + "*,,") .add("|"); - command.add(tar).add("-C").addFile(dir.toDirectory().toUnix()).add("-T", "-"); + command.add(tar).add("--no-recursion").add("-C").addFile(dir.toDirectory().toUnix()).add("-T", "-"); sc.command(command).execute(); } else { var command = CommandBuilder.of().add(tar); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java b/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java index 81a2b2208..da8f6be15 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java @@ -2,13 +2,14 @@ package io.xpipe.app.storage; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class DataStorageQuery { public static List queryUserInput(String input) { var connectionFilter = input != null && !input.isEmpty() && !input.equals("*") && !input.equals("**") - ? "**" + input + "**" + ? "**" + Pattern.quote(input) + "**" : "**"; var found = queryEntry("**", connectionFilter, "*"); if (found.size() > 1) { 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 5519fecee..5a0d9d23c 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java @@ -4,9 +4,7 @@ import io.xpipe.app.core.AppInstallation; import io.xpipe.app.core.AppNames; import io.xpipe.app.core.AppSystemInfo; import io.xpipe.app.issue.ErrorEventFactory; -import io.xpipe.app.process.CommandBuilder; -import io.xpipe.app.process.LocalShell; -import io.xpipe.app.process.OsFileSystem; +import io.xpipe.app.process.*; import io.xpipe.app.update.AppDistributionType; import java.io.IOException; @@ -25,17 +23,19 @@ public class DesktopShortcuts { } var icon = AppInstallation.ofCurrent().getLogoPath(); + var d = ShellDialects.POWERSHELL; var content = String.format(""" - $TARGET="%s" - $SHORTCUT="%s" - $ws = New-Object -ComObject WScript.Shell - $s = $ws.CreateShortcut("$SHORTCUT") - $S.IconLocation='%s' + $TARGET=%s + $SHORTCUT=%s + $ws=New-Object -ComObject WScript.Shell + $s=$ws.CreateShortcut("$SHORTCUT") + $S.IconLocation=%s $S.WindowStyle=7 - $S.TargetPath = "$TARGET" - $S.Arguments = '%s' + $S.TargetPath="$TARGET" + $S.Arguments=%s $S.Save() - """, executable, shortcutPath, icon, args).replaceAll("\n", ";"); + """, d.fileArgument(executable), d.fileArgument(shortcutPath.toString()), + d.fileArgument(icon.toString()), d.literalArgument(args)).replace("\n", ";"); shell.get().command(content).execute(); return shortcutPath; } diff --git a/app/src/main/java/io/xpipe/app/util/GroupFile.java b/app/src/main/java/io/xpipe/app/util/GroupFile.java index 244778e51..df4aa9d69 100644 --- a/app/src/main/java/io/xpipe/app/util/GroupFile.java +++ b/app/src/main/java/io/xpipe/app/util/GroupFile.java @@ -7,6 +7,7 @@ import lombok.Getter; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.OptionalInt; @Getter @@ -20,17 +21,13 @@ public class GroupFile { return f; } - public OptionalInt getGidForGroupIfPresent(String name) { + public Optional getGidForGroupIfPresent(String name) { var found = groups.entrySet().stream() .filter(e -> e.getValue().equals(name)) .findFirst() .map(e -> e.getKey()) .orElse(null); - return found != null ? OptionalInt.of(found) : OptionalInt.empty(); - } - - public int getGidForGroup(String name) { - return getGidForGroupIfPresent(name).orElse(0); + return found != null ? Optional.of(found) : Optional.empty(); } private void loadGroups(ShellControl sc) throws Exception { diff --git a/app/src/main/java/io/xpipe/app/util/PasswdFile.java b/app/src/main/java/io/xpipe/app/util/PasswdFile.java index 169b2a5ff..d5388805e 100644 --- a/app/src/main/java/io/xpipe/app/util/PasswdFile.java +++ b/app/src/main/java/io/xpipe/app/util/PasswdFile.java @@ -7,6 +7,7 @@ import lombok.Getter; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.OptionalInt; @Getter @@ -20,13 +21,13 @@ public class PasswdFile { return passwdFile; } - public OptionalInt getUidForUserIfPresent(String name) { + public Optional getUidForUserIfPresent(String name) { var found = users.entrySet().stream() .filter(e -> e.getValue().equals(name)) .findFirst() .map(e -> e.getKey()) .orElse(null); - return found != null ? OptionalInt.of(found) : OptionalInt.empty(); + return found != null ? Optional.of(found) : Optional.empty(); } public int getUidForUser(String name) {