From 8d6eb1051ccdd12248961511568d7a147866c7aa Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 6 May 2024 02:11:46 +0000 Subject: [PATCH] Refactor file transfers --- .../xpipe/app/browser/BrowserClipboard.java | 19 +- .../app/browser/BrowserSelectionListComp.java | 18 +- .../app/browser/BrowserStatusBarComp.java | 4 +- .../app/browser/BrowserTransferComp.java | 20 +- .../app/browser/BrowserTransferModel.java | 31 +- .../app/browser/BrowserTransferProgress.java | 25 +- .../app/browser/file/BrowserContextMenu.java | 5 +- .../xpipe/app/browser/file/BrowserEntry.java | 20 +- .../app/browser/file/BrowserFileListComp.java | 51 +-- .../file/BrowserFileListCompEntry.java | 8 +- .../browser/file/BrowserFileListModel.java | 10 +- .../browser/file/BrowserFileOverviewComp.java | 2 +- .../file/BrowserFileTransferOperation.java | 288 +++++++++++++++++ .../file/BrowserQuickAccessContextMenu.java | 2 +- .../app/browser/file/FileSystemHelper.java | 303 +----------------- .../app/browser/file/LocalFileSystem.java | 42 +++ .../app/browser/fs/OpenFileSystemModel.java | 11 +- .../java/io/xpipe/app/core/mode/GuiMode.java | 7 +- .../io/xpipe/ext/base/browser/CopyAction.java | 2 +- .../xpipe/ext/base/browser/PasteAction.java | 2 +- 20 files changed, 435 insertions(+), 435 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java create mode 100644 app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java index fc4c0f78..21b9f7b3 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java @@ -1,17 +1,16 @@ package io.xpipe.app.browser; -import io.xpipe.app.browser.file.FileSystemHelper; +import io.xpipe.app.browser.file.BrowserEntry; +import io.xpipe.app.browser.file.LocalFileSystem; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.store.FileSystem; import io.xpipe.core.util.FailableRunnable; - import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.input.ClipboardContent; import javafx.scene.input.Dragboard; - import lombok.SneakyThrows; import lombok.Value; @@ -45,14 +44,14 @@ public class BrowserClipboard { List data = (List) clipboard.getData(DataFlavor.javaFileListFlavor); var files = - data.stream().map(string -> string.toPath()).toList(); + data.stream().map(f -> f.toPath()).toList(); if (files.size() == 0) { return; } - var entries = new ArrayList(); + var entries = new ArrayList(); for (Path file : files) { - entries.add(FileSystemHelper.getLocal(file)); + entries.add(LocalFileSystem.getLocalBrowserEntry(file)); } currentCopyClipboard.setValue(new Instance(UUID.randomUUID(), null, entries)); @@ -64,7 +63,7 @@ public class BrowserClipboard { } @SneakyThrows - public static ClipboardContent startDrag(FileSystem.FileEntry base, List selected) { + public static ClipboardContent startDrag(FileSystem.FileEntry base, List selected) { if (selected.isEmpty()) { return null; } @@ -77,7 +76,7 @@ public class BrowserClipboard { } @SneakyThrows - public static void startCopy(FileSystem.FileEntry base, List selected) { + public static void startCopy(FileSystem.FileEntry base, List selected) { if (selected.isEmpty()) { currentCopyClipboard.setValue(null); return; @@ -118,11 +117,11 @@ public class BrowserClipboard { public static class Instance { UUID uuid; FileSystem.FileEntry baseDirectory; - List entries; + List entries; public String toClipboardString() { return entries.stream() - .map(fileEntry -> "\"" + fileEntry.getPath() + "\"") + .map(fileEntry -> "\"" + fileEntry.getRawFileEntry().getPath() + "\"") .collect(Collectors.joining(ProcessControlProvider.get() .getEffectiveLocalDialect() .getNewLine() diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java index e1712037..02edb3d4 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java @@ -1,6 +1,6 @@ package io.xpipe.app.browser; -import io.xpipe.app.browser.icon.FileIconManager; +import io.xpipe.app.browser.file.BrowserEntry; import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.core.AppStyle; import io.xpipe.app.core.AppWindowHelper; @@ -8,9 +8,6 @@ import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; import io.xpipe.app.fxcomps.util.PlatformThread; -import io.xpipe.core.store.FileNames; -import io.xpipe.core.store.FileSystem; - import javafx.beans.property.SimpleStringProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; @@ -21,7 +18,6 @@ import javafx.scene.control.OverrunStyle; import javafx.scene.image.Image; import javafx.scene.layout.Region; import javafx.scene.paint.Color; - import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Value; @@ -33,14 +29,14 @@ import java.util.function.Function; @AllArgsConstructor public class BrowserSelectionListComp extends SimpleComp { - ObservableList list; - Function> nameTransformation; + ObservableList list; + Function> nameTransformation; - public BrowserSelectionListComp(ObservableList list) { - this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath()))); + public BrowserSelectionListComp(ObservableList list) { + this(list, entry -> new SimpleStringProperty(entry.getFileName())); } - public static Image snapshot(ObservableList list) { + public static Image snapshot(ObservableList list) { var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion(); var scene = new Scene(r); AppWindowHelper.setupStylesheets(scene); @@ -54,7 +50,7 @@ public class BrowserSelectionListComp extends SimpleComp { protected Region createSimple() { var c = new ListBoxViewComp<>(list, list, entry -> { return Comp.of(() -> { - var image = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24) + var image = PrettyImageHelper.ofFixedSizeSquare(entry.getIcon(), 24) .createRegion(); var l = new Label(null, image); l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java index 249ea23e..84681fe9 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -87,9 +87,7 @@ public class BrowserStatusBarComp extends SimpleComp { var allCount = Bindings.createIntegerBinding( () -> { - return (int) model.getFileList().getAll().getValue().stream() - .filter(entry -> !entry.isSynthetic()) - .count(); + return model.getFileList().getAll().getValue().size(); }, model.getFileList().getAll()); var selectedComp = new LabelComp(Bindings.createStringBinding( diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java index 52320444..fd304422 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java @@ -9,10 +9,7 @@ import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment; import io.xpipe.app.fxcomps.impl.*; import io.xpipe.app.fxcomps.util.ListBindingsHelper; import io.xpipe.app.fxcomps.util.PlatformThread; -import io.xpipe.app.storage.DataStorage; import io.xpipe.core.process.OsType; -import io.xpipe.core.store.FileNames; - import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.geometry.Insets; @@ -21,7 +18,6 @@ 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; import java.io.File; @@ -50,25 +46,21 @@ public class BrowserTransferComp extends SimpleComp { var backgroundStack = new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); - var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getFileEntry()); + var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry()); var list = new BrowserSelectionListComp( binding, entry -> Bindings.createStringBinding( () -> { var sourceItem = syncItems.stream() - .filter(item -> item.getFileEntry() == entry) + .filter(item -> item.getBrowserEntry() == entry) .findAny(); if (sourceItem.isEmpty()) { return "?"; } - var name = - sourceItem.get().downloadFinished().get() + var name = entry.getModel() == null || sourceItem.get().downloadFinished().get() ? "Local" - : DataStorage.get() - .getStoreDisplayName(entry.getFileSystem() - .getStore()) - .orElse("?"); - return FileNames.getFileName(entry.getPath()) + " (" + name + ")"; + : entry.getModel().getFileSystemModel().getName(); + return entry.getFileName() + " (" + name + ")"; }, syncAllDownloaded)) .apply(struc -> struc.get().setMinHeight(150)) @@ -154,7 +146,7 @@ public class BrowserTransferComp extends SimpleComp { } var selected = syncItems.stream() - .map(BrowserTransferModel.Item::getFileEntry) + .map(item -> item.getBrowserEntry()) .toList(); Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java index 41c2146a..e769a317 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java @@ -1,14 +1,13 @@ package io.xpipe.app.browser; -import io.xpipe.app.browser.file.FileSystemHelper; +import io.xpipe.app.browser.file.BrowserEntry; +import io.xpipe.app.browser.file.BrowserFileTransferOperation; +import io.xpipe.app.browser.file.LocalFileSystem; import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.browser.session.BrowserSessionModel; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.ShellTemp; -import io.xpipe.core.store.FileNames; -import io.xpipe.core.store.FileSystem; - import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; @@ -17,7 +16,6 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableBooleanValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; - import lombok.Value; import org.apache.commons.io.FileUtils; @@ -66,9 +64,9 @@ public class BrowserTransferModel { items.clear(); } - public void drop(OpenFileSystemModel model, List entries) { + public void drop(OpenFileSystemModel model, List entries) { entries.forEach(entry -> { - var name = FileNames.getFileName(entry.getPath()); + var name = entry.getFileName(); if (items.stream().anyMatch(item -> item.getName().equals(name))) { return; } @@ -89,14 +87,14 @@ public class BrowserTransferModel { try { var paths = entries.stream().map(File::toPath).filter(Files::exists).toList(); for (Path path : paths) { - var entry = FileSystemHelper.getLocal(path); - var name = entry.getName(); + var entry = LocalFileSystem.getLocalBrowserEntry(path); + var name = entry.getFileName(); if (items.stream().anyMatch(item -> item.getName().equals(name))) { return; } var item = new Item(null, name, entry, path); - item.progress.setValue(BrowserTransferProgress.finished(entry.getName(), entry.getSize())); + item.progress.setValue(BrowserTransferProgress.finished(entry.getFileName(), entry.getRawFileEntry().getSize())); items.add(item); } } catch (Exception ex) { @@ -128,15 +126,16 @@ public class BrowserTransferModel { try { try (var b = new BooleanScope(downloading).start()) { - FileSystemHelper.dropFilesInto( - FileSystemHelper.getLocal(TEMP), - List.of(item.getFileEntry()), + var op = new BrowserFileTransferOperation( + LocalFileSystem.getLocalFileEntry(TEMP), + List.of(item.getBrowserEntry().getRawFileEntry()), true, false, progress -> { item.getProgress().setValue(progress); item.getOpenFileSystemModel().getProgress().setValue(progress); }); + op.execute(); } } catch (Throwable t) { ErrorEvent.fromThrowable(t).handle(); @@ -151,15 +150,15 @@ public class BrowserTransferModel { public static class Item { OpenFileSystemModel openFileSystemModel; String name; - FileSystem.FileEntry fileEntry; + BrowserEntry browserEntry; Path localFile; Property progress; public Item( - OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) { + OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) { this.openFileSystemModel = openFileSystemModel; this.name = name; - this.fileEntry = fileEntry; + this.browserEntry = browserEntry; this.localFile = localFile; this.progress = new SimpleObjectProperty<>(); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java index 73860641..d909c64d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java @@ -2,26 +2,45 @@ package io.xpipe.app.browser; import lombok.Value; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + @Value public class BrowserTransferProgress { String name; long transferred; long total; + Instant start; public static BrowserTransferProgress empty() { - return new BrowserTransferProgress(null, 0, 0); + return new BrowserTransferProgress(null, 0, 0, Instant.now()); } static BrowserTransferProgress empty(String name, long size) { - return new BrowserTransferProgress(name, 0, size); + return new BrowserTransferProgress(name, 0, size, Instant.now()); } public static BrowserTransferProgress finished(String name, long size) { - return new BrowserTransferProgress(name, size, size); + return new BrowserTransferProgress(name, size, size, Instant.now()); } public boolean done() { return transferred >= total; } + + public Duration elapsedTime() { + var now = Instant.now(); + var elapsed = Duration.between(start,now); + return elapsed; + } + + public Duration expectedTimeRemaining() { + var elapsed = elapsedTime(); + var share = (double) transferred / total; + var rest = 1.0 - share; + var restMillis = (long) (elapsed.toMillis() * rest); + return Duration.of(restMillis, ChronoUnit.MILLIS); + } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java index 538f1104..08e3e515 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java @@ -32,8 +32,7 @@ public final class BrowserContextMenu extends ContextMenu { ? selected.stream() .map(browserEntry -> new BrowserEntry( browserEntry.getRawFileEntry().resolved(), - browserEntry.getModel(), - browserEntry.isSynthetic())) + browserEntry.getModel())) .toList() : selected; } @@ -44,7 +43,7 @@ public final class BrowserContextMenu extends ContextMenu { var empty = source == null; var selected = new ArrayList<>( empty - ? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false)) + ? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList())) : model.getFileList().getSelection()); if (source != null && !selected.contains(source)) { selected.add(source); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java index 14f523ce..de226a24 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java @@ -13,14 +13,12 @@ public class BrowserEntry { private final BrowserFileListModel model; private final FileSystem.FileEntry rawFileEntry; - private final boolean synthetic; private final BrowserIconFileType fileType; private final BrowserIconDirectoryType directoryType; - public BrowserEntry(FileSystem.FileEntry rawFileEntry, BrowserFileListModel model, boolean synthetic) { + public BrowserEntry(FileSystem.FileEntry rawFileEntry, BrowserFileListModel model) { this.rawFileEntry = rawFileEntry; this.model = model; - this.synthetic = synthetic; this.fileType = fileType(rawFileEntry); this.directoryType = directoryType(rawFileEntry); } @@ -52,6 +50,17 @@ public class BrowserEntry { return null; } + public String getIcon() { + if (fileType != null) { + return fileType.getIcon(); + } else if (directoryType != null) { + return directoryType.getIcon(rawFileEntry, false); + } else { + return rawFileEntry.getKind() == FileKind.DIRECTORY + ? "default_folder.svg" + : "default_file.svg"; + } + } public String getFileName() { return getRawFileEntry().getName(); @@ -61,9 +70,4 @@ public class BrowserEntry { var n = getFileName(); return FileNames.quoteIfNecessary(n); } - - public String getOptionallyQuotedFilePath() { - var n = rawFileEntry.getPath(); - return FileNames.quoteIfNecessary(n); - } } 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 ede8e24c..8c9c52c6 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 @@ -1,7 +1,8 @@ package io.xpipe.app.browser.file; +import atlantafx.base.controls.Spacer; +import atlantafx.base.theme.Styles; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.comp.base.LazyTextFieldComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.SimpleComp; @@ -16,7 +17,6 @@ import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileKind; import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileSystem; - import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -38,9 +38,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import atlantafx.base.controls.Spacer; -import atlantafx.base.theme.Styles; - import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; @@ -137,29 +134,7 @@ public final class BrowserFileListComp extends SimpleComp { table.getSelectionModel().setCellSelectionEnabled(false); table.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { - var toSelect = new ArrayList<>(c.getList()); - // Explicitly unselect synthetic entries since we can't use a custom selection model as that is bugged in - // JavaFX - toSelect.removeIf(entry -> fileList.getFileSystemModel().getCurrentParentDirectory() != null - && entry.getRawFileEntry() - .getPath() - .equals(fileList.getFileSystemModel() - .getCurrentParentDirectory() - .getPath())); - // Remove unsuitable selection - toSelect.removeIf(browserEntry -> (browserEntry.getRawFileEntry().getKind() == FileKind.DIRECTORY - && !fileList.getSelectionMode().isAcceptsDirectories()) - || (browserEntry.getRawFileEntry().getKind() != FileKind.DIRECTORY - && !fileList.getSelectionMode().isAcceptsFiles())); - fileList.getSelection().setAll(toSelect); - - Platform.runLater(() -> { - var toUnselect = table.getSelectionModel().getSelectedItems().stream() - .filter(entry -> !toSelect.contains(entry)) - .toList(); - toUnselect.forEach(entry -> table.getSelectionModel() - .clearSelection(table.getItems().indexOf(entry))); - }); + fileList.getSelection().setAll(c.getList()); }); fileList.getSelection().addListener((ListChangeListener) c -> { @@ -174,7 +149,6 @@ public final class BrowserFileListComp extends SimpleComp { } var indices = c.getList().stream() - .skip(1) .mapToInt(entry -> table.getItems().indexOf(entry)) .toArray(); table.getSelectionModel() @@ -276,10 +250,6 @@ public final class BrowserFileListComp extends SimpleComp { }, null, () -> { - if (row.getItem() != null && row.getItem().isSynthetic()) { - return null; - } - return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem()); }) .augment(new SimpleCompStructure<>(row)); @@ -573,15 +543,7 @@ public final class BrowserFileListComp extends SimpleComp { // Visibility seems to be bugged, so use opacity setOpacity(0.0); } else { - var isParentLink = getTableRow() - .getItem() - .getRawFileEntry() - .equals(fileList.getFileSystemModel().getCurrentParentDirectory()); - img.set(FileIconManager.getFileIcon( - isParentLink - ? fileList.getFileSystemModel().getCurrentDirectory() - : getTableRow().getItem().getRawFileEntry(), - isParentLink)); + img.set(getTableRow().getItem().getIcon()); var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY; pseudoClassStateChanged(FOLDER, isDirectory); @@ -594,9 +556,8 @@ public final class BrowserFileListComp extends SimpleComp { .resolved() .getPath() : getTableRow().getItem().getFileName(); - var fileName = isParentLink ? ".." : normalName; - var hidden = !isParentLink - && (getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith(".")); + var fileName = normalName; + var hidden = getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith("."); getTableRow().pseudoClassStateChanged(HIDDEN, hidden); text.set(fileName); // Visibility seems to be bugged, so use opacity diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java index c6b5a4b3..44f01677 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java @@ -109,7 +109,7 @@ public class BrowserFileListCompEntry { if (!Objects.equals( model.getFileSystemModel().getFileSystem(), - cb.getEntries().getFirst().getFileSystem())) { + cb.getEntries().getFirst().getRawFileEntry().getFileSystem())) { return true; } @@ -157,7 +157,7 @@ public class BrowserFileListCompEntry { var target = item != null && item.getRawFileEntry().getKind() == FileKind.DIRECTORY ? item.getRawFileEntry() : model.getFileSystemModel().getCurrentDirectory(); - model.getFileSystemModel().dropFilesIntoAsync(target, files, false); + model.getFileSystemModel().dropFilesIntoAsync(target, files.stream().map(browserEntry -> browserEntry.getRawFileEntry()).toList(), false); event.setDropCompleted(true); event.consume(); } @@ -182,7 +182,7 @@ public class BrowserFileListCompEntry { return; } - var selected = model.getSelectedRaw(); + var selected = model.getSelection(); Dragboard db = row.startDragAndDrop(TransferMode.COPY); db.setContent(BrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected)); @@ -244,7 +244,7 @@ public class BrowserFileListCompEntry { return; } - if (item == null || item.isSynthetic()) { + if (item == null) { return; } 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 5f2dd000..3012d44e 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 @@ -59,10 +59,7 @@ public final class BrowserFileListModel { public void setAll(Stream newFiles) { try (var s = newFiles) { - var parent = fileSystemModel.getCurrentParentDirectory(); - var l = Stream.concat( - parent != null ? Stream.of(new BrowserEntry(parent, this, true)) : Stream.of(), - s.filter(entry -> entry != null).map(entry -> new BrowserEntry(entry, this, false))) + var l = s.filter(entry -> entry != null).map(entry -> new BrowserEntry(entry, this)) .toList(); all.setValue(l); refreshShown(); @@ -94,14 +91,13 @@ public final class BrowserFileListModel { } public Comparator order() { - var syntheticFirst = Comparator.comparing(path -> !path.isSynthetic()); var dirsFirst = Comparator.comparing( path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); var comp = comparatorProperty.getValue(); Comparator us = comp != null - ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) - : syntheticFirst.thenComparing(dirsFirst); + ? dirsFirst.thenComparing(comp) + : dirsFirst; return us; } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java index e364c844..863a80d3 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java @@ -37,7 +37,7 @@ public class BrowserFileOverviewComp extends SimpleComp { var graphic = new HorizontalComp(List.of( icon, new BrowserQuickAccessButtonComp( - () -> new BrowserEntry(entry, model.getFileList(), false), model))); + () -> new BrowserEntry(entry, model.getFileList()), model))); var l = new Button(entry.getPath(), graphic.createRegion()); l.setGraphicTextGap(1); l.setOnAction(event -> { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java new file mode 100644 index 00000000..5c093b78 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java @@ -0,0 +1,288 @@ +package io.xpipe.app.browser.file; + +import io.xpipe.app.browser.BrowserTransferProgress; +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FileNames; +import io.xpipe.core.store.FilePath; +import io.xpipe.core.store.FileSystem; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +public class BrowserFileTransferOperation { + + private final FileSystem.FileEntry target; + private final List files; + private final boolean explicitCopy; + private final boolean checkConflicts; + private final Consumer progress; + + BrowserAlerts.FileConflictChoice lastConflictChoice; + + public BrowserFileTransferOperation(FileSystem.FileEntry target, List files, boolean explicitCopy, boolean checkConflicts, + Consumer progress + ) { + this.target = target; + this.files = files; + this.explicitCopy = explicitCopy; + this.checkConflicts = checkConflicts; + this.progress = progress; + } + + public static BrowserFileTransferOperation ofLocal(FileSystem.FileEntry target, List files, boolean explicitCopy, boolean checkConflicts, Consumer progress) { + var entries = files.stream() + .map(path -> { + try { + return LocalFileSystem.getLocalFileEntry(path); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .toList(); + return new BrowserFileTransferOperation(target, entries, explicitCopy, checkConflicts, progress); + } + + private void updateProgress(BrowserTransferProgress progress) { + this.progress.accept(progress); + } + + private boolean handleChoice( + FileSystem fileSystem, + String target, + boolean multiple) + throws Exception { + if (lastConflictChoice == BrowserAlerts.FileConflictChoice.CANCEL) { + return false; + } + + if (lastConflictChoice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { + return true; + } + + if (fileSystem.fileExists(target)) { + if (lastConflictChoice == BrowserAlerts.FileConflictChoice.SKIP_ALL) { + return false; + } + + var choice = BrowserAlerts.showFileConflictAlert(target, multiple); + if (choice == BrowserAlerts.FileConflictChoice.CANCEL) { + lastConflictChoice = BrowserAlerts.FileConflictChoice.CANCEL; + return false; + } + + if (choice == BrowserAlerts.FileConflictChoice.SKIP) { + return false; + } + + if (choice == BrowserAlerts.FileConflictChoice.SKIP_ALL) { + lastConflictChoice = BrowserAlerts.FileConflictChoice.SKIP_ALL; + return false; + } + + if (choice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { + lastConflictChoice = BrowserAlerts.FileConflictChoice.REPLACE_ALL; + return true; + } + } + return true; + } + + public void execute() + throws Exception { + if (files.isEmpty()) { + updateProgress(BrowserTransferProgress.empty()); + return; + } + + var same = files.getFirst().getFileSystem().equals(target.getFileSystem()); + if (same && !explicitCopy) { + if (!BrowserAlerts.showMoveAlert(files, target)) { + return; + } + } + + for (var file : files) { + if (file.getFileSystem().equals(target.getFileSystem())) { + handleSingleOnSameFileSystem(file); + updateProgress(BrowserTransferProgress.finished(file.getName(), file.getSize())); + } else { + handleSingleAcrossFileSystems(file); + } + } + } + + private void handleSingleOnSameFileSystem(FileSystem.FileEntry source) + throws Exception { + // Prevent dropping directory into itself + if (source.getPath().equals(target.getPath())) { + return; + } + + var sourceFile = source.getPath(); + var targetFile = FileNames.join(target.getPath(), FileNames.getFileName(sourceFile)); + + if (sourceFile.equals(targetFile)) { + return; + } + + if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) { + throw ErrorEvent.expected( + new IllegalArgumentException("Target directory " + targetFile + " does already exist")); + } + + if (checkConflicts && !handleChoice(target.getFileSystem(), targetFile, files.size() > 1)) { + return; + } + + if (explicitCopy) { + target.getFileSystem().copy(sourceFile, targetFile); + } else { + target.getFileSystem().move(sourceFile, targetFile); + } + } + + private void handleSingleAcrossFileSystems(FileSystem.FileEntry source) + throws Exception { + if (target.getKind() != FileKind.DIRECTORY) { + throw new IllegalStateException("Target " + target.getPath() + " is not a directory"); + } + + var flatFiles = new LinkedHashMap(); + + // Prevent dropping directory into itself + if (source.getFileSystem().equals(target.getFileSystem()) + && FileNames.startsWith(source.getPath(), target.getPath())) { + return; + } + + AtomicLong totalSize = new AtomicLong(); + if (source.getKind() == FileKind.DIRECTORY) { + var directoryName = FileNames.getFileName(source.getPath()); + flatFiles.put(source, directoryName); + + var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath())); + List list = source.getFileSystem().listFilesRecursively(source.getPath()); + for (FileSystem.FileEntry fileEntry : list) { + var rel = FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath())); + flatFiles.put(fileEntry, rel); + if (fileEntry.getKind() == FileKind.FILE) { + // This one is up-to-date and does not need to be recalculated + totalSize.addAndGet(fileEntry.getSize()); + } + } + } else { + flatFiles.put(source, FileNames.getFileName(source.getPath())); + // Recalculate as it could have been changed meanwhile + totalSize.addAndGet(source.getFileSystem().getFileSize(source.getPath())); + } + + AtomicLong transferred = new AtomicLong(); + for (var e : flatFiles.entrySet()) { + var sourceFile = e.getKey(); + var fixedRelPath = new FilePath(e.getValue()) + .fileSystemCompatible( + target.getFileSystem().getShell().orElseThrow().getOsType()); + var targetFile = FileNames.join(target.getPath(), fixedRelPath.toString()); + if (sourceFile.getFileSystem().equals(target.getFileSystem())) { + throw new IllegalStateException(); + } + + if (sourceFile.getKind() == FileKind.DIRECTORY) { + target.getFileSystem().mkdirs(targetFile); + } else if (sourceFile.getKind() == FileKind.FILE) { + if (checkConflicts + && !handleChoice( + target.getFileSystem(), + targetFile, + files.size() > 1 || flatFiles.size() > 1)) { + continue; + } + + InputStream inputStream = null; + OutputStream outputStream = null; + try { + var fileSize = sourceFile.getFileSystem().getFileSize(sourceFile.getPath()); + inputStream = sourceFile.getFileSystem().openInput(sourceFile.getPath()); + outputStream = target.getFileSystem().openOutput(targetFile, fileSize); + transferFile(sourceFile, inputStream, outputStream, transferred, totalSize); + inputStream.transferTo(OutputStream.nullOutputStream()); + } catch (Exception ex) { + // Mark progress as finished to reset any progress display + updateProgress(BrowserTransferProgress.finished(sourceFile.getName(), transferred.get())); + + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception om) { + // This is expected as the process control has to be killed + // When calling close, it will throw an exception when it has to kill + // ErrorEvent.fromThrowable(om).handle(); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (Exception om) { + // This is expected as the process control has to be killed + // When calling close, it will throw an exception when it has to kill + // ErrorEvent.fromThrowable(om).handle(); + } + } + throw ex; + } + + updateProgress(BrowserTransferProgress.finished(sourceFile.getName(), transferred.get())); + Exception exception = null; + try { + inputStream.close(); + } catch (Exception om) { + exception = om; + } + try { + outputStream.close(); + } catch (Exception om) { + if (exception != null) { + ErrorEvent.fromThrowable(om).handle(); + } else { + exception = om; + } + } + if (exception != null) { + throw exception; + } + } + } + updateProgress(BrowserTransferProgress.finished(source.getName(), totalSize.get())); + } + + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private void transferFile( + FileSystem.FileEntry sourceFile, + InputStream inputStream, + OutputStream outputStream, + AtomicLong transferred, + AtomicLong total) + throws IOException { + // Initialize progress immediately prior to reading anything + var now = Instant.now(); + updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), now)); + + var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize()); + byte[] buffer = new byte[bs]; + int read; + while ((read = inputStream.read(buffer, 0, bs)) > 0) { + outputStream.write(buffer, 0, read); + transferred.addAndGet(read); + updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), now)); + } + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index 066df269..41260cdf 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -101,7 +101,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { newItems.add(empty); } else { var browserEntries = list.stream() - .map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList(), false)) + .map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList())) .toList(); var menus = browserEntries.stream() .sorted(model.getFileList().order()) diff --git a/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java index 1dabdd69..26a5a8df 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java @@ -1,28 +1,17 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.BrowserTransferProgress; import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.process.OsType; -import io.xpipe.core.store.*; +import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FileNames; +import io.xpipe.core.store.FileSystem; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.time.Instant; -import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; public class FileSystemHelper { - private static final int DEFAULT_BUFFER_SIZE = 1024; - private static FileSystem localFileSystem; - public static String adjustPath(OpenFileSystemModel model, String path) { if (path == null) { return null; @@ -134,23 +123,6 @@ public class FileSystemHelper { } } - public static FileSystem.FileEntry getLocal(Path file) throws Exception { - if (localFileSystem == null) { - localFileSystem = new LocalStore().createFileSystem(); - localFileSystem.open(); - } - - return new FileSystem.FileEntry( - localFileSystem, - file.toString(), - Files.getLastModifiedTime(file).toInstant(), - Files.isHidden(file), - Files.isExecutable(file), - Files.size(file), - null, - Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE); - } - public static FileSystem.FileEntry getRemoteWrapper(FileSystem fileSystem, String file) throws Exception { return new FileSystem.FileEntry( fileSystem, @@ -163,24 +135,6 @@ public class FileSystemHelper { fileSystem.directoryExists(file) ? FileKind.DIRECTORY : FileKind.FILE); } - public static void dropLocalFilesInto( - FileSystem.FileEntry entry, - List files, - Consumer progress, - boolean checkConflicts) - throws Exception { - var entries = files.stream() - .map(path -> { - try { - return getLocal(path); - } catch (Exception e) { - throw new RuntimeException(e); - } - }) - .toList(); - dropFilesInto(entry, entries, false, checkConflicts, progress); - } - public static void delete(List files) { if (files.isEmpty()) { return; @@ -194,255 +148,4 @@ public class FileSystemHelper { } } } - - public static void dropFilesInto( - FileSystem.FileEntry target, - List files, - boolean explicitCopy, - boolean checkConflicts, - Consumer progress) - throws Exception { - if (files.isEmpty()) { - progress.accept(BrowserTransferProgress.empty()); - return; - } - - var same = files.getFirst().getFileSystem().equals(target.getFileSystem()); - if (same && !explicitCopy) { - if (!BrowserAlerts.showMoveAlert(files, target)) { - return; - } - } - - AtomicReference lastConflictChoice = new AtomicReference<>(); - for (var file : files) { - if (file.getFileSystem().equals(target.getFileSystem())) { - dropFileAcrossSameFileSystem( - target, file, explicitCopy, lastConflictChoice, files.size() > 1, checkConflicts); - progress.accept(BrowserTransferProgress.finished(file.getName(), file.getSize())); - } else { - dropFileAcrossFileSystems(target, file, progress, lastConflictChoice, files.size() > 1, checkConflicts); - } - } - } - - private static void dropFileAcrossSameFileSystem( - FileSystem.FileEntry target, - FileSystem.FileEntry source, - boolean explicitCopy, - AtomicReference lastConflictChoice, - boolean multiple, - boolean checkConflicts) - throws Exception { - // Prevent dropping directory into itself - if (source.getPath().equals(target.getPath())) { - return; - } - - var sourceFile = source.getPath(); - var targetFile = FileNames.join(target.getPath(), FileNames.getFileName(sourceFile)); - - if (sourceFile.equals(targetFile)) { - return; - } - - if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) { - throw ErrorEvent.expected( - new IllegalArgumentException("Target directory " + targetFile + " does already exist")); - } - - if (checkConflicts && !handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple)) { - return; - } - - if (explicitCopy) { - target.getFileSystem().copy(sourceFile, targetFile); - } else { - target.getFileSystem().move(sourceFile, targetFile); - } - } - - private static void dropFileAcrossFileSystems( - FileSystem.FileEntry target, - FileSystem.FileEntry source, - Consumer progress, - AtomicReference lastConflictChoice, - boolean multiple, - boolean checkConflicts) - throws Exception { - if (target.getKind() != FileKind.DIRECTORY) { - throw new IllegalStateException("Target " + target.getPath() + " is not a directory"); - } - - var flatFiles = new LinkedHashMap(); - - // Prevent dropping directory into itself - if (source.getFileSystem().equals(target.getFileSystem()) - && FileNames.startsWith(source.getPath(), target.getPath())) { - return; - } - - AtomicLong totalSize = new AtomicLong(); - if (source.getKind() == FileKind.DIRECTORY) { - var directoryName = FileNames.getFileName(source.getPath()); - flatFiles.put(source, directoryName); - - var baseRelative = FileNames.toDirectory(FileNames.getParent(source.getPath())); - List list = source.getFileSystem().listFilesRecursively(source.getPath()); - for (FileSystem.FileEntry fileEntry : list) { - var rel = FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath())); - flatFiles.put(fileEntry, rel); - if (fileEntry.getKind() == FileKind.FILE) { - // This one is up-to-date and does not need to be recalculated - totalSize.addAndGet(fileEntry.getSize()); - } - } - } else { - flatFiles.put(source, FileNames.getFileName(source.getPath())); - // Recalculate as it could have been changed meanwhile - totalSize.addAndGet(source.getFileSystem().getFileSize(source.getPath())); - } - - AtomicLong transferred = new AtomicLong(); - for (var e : flatFiles.entrySet()) { - var sourceFile = e.getKey(); - var fixedRelPath = new FilePath(e.getValue()) - .fileSystemCompatible( - target.getFileSystem().getShell().orElseThrow().getOsType()); - var targetFile = FileNames.join(target.getPath(), fixedRelPath.toString()); - if (sourceFile.getFileSystem().equals(target.getFileSystem())) { - throw new IllegalStateException(); - } - - if (sourceFile.getKind() == FileKind.DIRECTORY) { - target.getFileSystem().mkdirs(targetFile); - } else if (sourceFile.getKind() == FileKind.FILE) { - if (checkConflicts - && !handleChoice( - lastConflictChoice, - target.getFileSystem(), - targetFile, - multiple || flatFiles.size() > 1)) { - continue; - } - - InputStream inputStream = null; - OutputStream outputStream = null; - try { - var fileSize = sourceFile.getFileSystem().getFileSize(sourceFile.getPath()); - inputStream = sourceFile.getFileSystem().openInput(sourceFile.getPath()); - outputStream = target.getFileSystem().openOutput(targetFile, fileSize); - transferFile(sourceFile, inputStream, outputStream, transferred, totalSize, progress); - inputStream.transferTo(OutputStream.nullOutputStream()); - } catch (Exception ex) { - // Mark progress as finished to reset any progress display - progress.accept(BrowserTransferProgress.finished(sourceFile.getName(), transferred.get())); - - if (inputStream != null) { - try { - inputStream.close(); - } catch (Exception om) { - // This is expected as the process control has to be killed - // When calling close, it will throw an exception when it has to kill - // ErrorEvent.fromThrowable(om).handle(); - } - } - if (outputStream != null) { - try { - outputStream.close(); - } catch (Exception om) { - // This is expected as the process control has to be killed - // When calling close, it will throw an exception when it has to kill - // ErrorEvent.fromThrowable(om).handle(); - } - } - throw ex; - } - - progress.accept(BrowserTransferProgress.finished(sourceFile.getName(), transferred.get())); - Exception exception = null; - try { - inputStream.close(); - } catch (Exception om) { - exception = om; - } - try { - outputStream.close(); - } catch (Exception om) { - if (exception != null) { - ErrorEvent.fromThrowable(om).handle(); - } else { - exception = om; - } - } - if (exception != null) { - throw exception; - } - } - } - progress.accept(BrowserTransferProgress.finished(source.getName(), totalSize.get())); - } - - private static boolean handleChoice( - AtomicReference previous, - FileSystem fileSystem, - String target, - boolean multiple) - throws Exception { - if (previous.get() == BrowserAlerts.FileConflictChoice.CANCEL) { - return false; - } - - if (previous.get() == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { - return true; - } - - if (fileSystem.fileExists(target)) { - if (previous.get() == BrowserAlerts.FileConflictChoice.SKIP_ALL) { - return false; - } - - var choice = BrowserAlerts.showFileConflictAlert(target, multiple); - if (choice == BrowserAlerts.FileConflictChoice.CANCEL) { - previous.set(BrowserAlerts.FileConflictChoice.CANCEL); - return false; - } - - if (choice == BrowserAlerts.FileConflictChoice.SKIP) { - return false; - } - - if (choice == BrowserAlerts.FileConflictChoice.SKIP_ALL) { - previous.set(BrowserAlerts.FileConflictChoice.SKIP_ALL); - return false; - } - - if (choice == BrowserAlerts.FileConflictChoice.REPLACE_ALL) { - previous.set(BrowserAlerts.FileConflictChoice.REPLACE_ALL); - return true; - } - } - return true; - } - - private static void transferFile( - FileSystem.FileEntry sourceFile, - InputStream inputStream, - OutputStream outputStream, - AtomicLong transferred, - AtomicLong total, - Consumer progress) - throws IOException { - // Initialize progress immediately prior to reading anything - progress.accept(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get())); - - var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize()); - byte[] buffer = new byte[bs]; - int read; - while ((read = inputStream.read(buffer, 0, bs)) > 0) { - outputStream.write(buffer, 0, read); - transferred.addAndGet(read); - progress.accept(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get())); - } - } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java b/app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java new file mode 100644 index 00000000..2fe56c18 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java @@ -0,0 +1,42 @@ +package io.xpipe.app.browser.file; + +import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FileSystem; +import io.xpipe.core.store.LocalStore; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class LocalFileSystem { + + private static FileSystem localFileSystem; + + public static void init() throws Exception { + if (localFileSystem == null) { + localFileSystem = new LocalStore().createFileSystem(); + localFileSystem.open(); + } + } + + public static FileSystem.FileEntry getLocalFileEntry(Path file) throws IOException { + if (localFileSystem == null) { + throw new IllegalStateException(); + } + + return new FileSystem.FileEntry( + localFileSystem, + file.toString(), + Files.getLastModifiedTime(file).toInstant(), + Files.isHidden(file), + Files.isExecutable(file), + Files.size(file), + null, + Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE); + } + + public static BrowserEntry getLocalBrowserEntry(Path file) throws Exception { + var e = getLocalFileEntry(file); + return new BrowserEntry(e,null); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java index 125f9585..cd0b8771 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java @@ -4,6 +4,7 @@ import io.xpipe.app.browser.BrowserSavedState; import io.xpipe.app.browser.BrowserTransferProgress; import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.browser.file.BrowserFileListModel; +import io.xpipe.app.browser.file.BrowserFileTransferOperation; import io.xpipe.app.browser.file.FileSystemHelper; import io.xpipe.app.browser.session.BrowserAbstractSessionModel; import io.xpipe.app.browser.session.BrowserSessionModel; @@ -22,10 +23,8 @@ import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellOpenFunction; import io.xpipe.core.store.*; import io.xpipe.core.util.FailableConsumer; - import javafx.beans.binding.Bindings; import javafx.beans.property.*; - import lombok.Getter; import lombok.SneakyThrows; @@ -343,7 +342,8 @@ public final class OpenFileSystemModel extends BrowserSessionTab { - progress.setValue(browserTransferProgress); - }); + var op = new BrowserFileTransferOperation(target, files,false,true, progress::setValue); + op.execute(); refreshSync(); }); }); diff --git a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java index b0e1096c..528d69b7 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java @@ -1,5 +1,6 @@ package io.xpipe.app.core.mode; +import io.xpipe.app.browser.file.LocalFileSystem; import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.core.App; import io.xpipe.app.core.AppGreetings; @@ -49,9 +50,13 @@ public class GuiMode extends PlatformMode { }); TrackEvent.info("Window setup complete"); - ThreadHelper.runAsync(() -> { + // Can be loaded async + ThreadHelper.runFailableAsync(() -> { FileIconManager.loadIfNecessary(); }); + ThreadHelper.runFailableAsync(() -> { + LocalFileSystem.init(); + }); UpdateChangelogAlert.showIfNeeded(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java index 194c3de4..e1a2ab31 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java @@ -22,7 +22,7 @@ public class CopyAction implements LeafAction { public void execute(OpenFileSystemModel model, List entries) { BrowserClipboard.startCopy( model.getCurrentDirectory(), - entries.stream().map(entry -> entry.getRawFileEntry()).toList()); + entries); } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java index 48f1809d..636d0633 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java @@ -34,7 +34,7 @@ public class PasteAction implements LeafAction { return; } - model.dropFilesIntoAsync(target, files, true); + model.dropFilesIntoAsync(target, files.stream().map(browserEntry -> browserEntry.getRawFileEntry()).toList(), true); } @Override