mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Merge branch browser-model into master
This commit is contained in:
parent
b98ac6b4ea
commit
9d4c4fe97d
28 changed files with 520 additions and 497 deletions
|
@ -43,7 +43,6 @@ dependencies {
|
||||||
api 'info.picocli:picocli:4.7.5'
|
api 'info.picocli:picocli:4.7.5'
|
||||||
api 'org.kohsuke:github-api:1.321'
|
api 'org.kohsuke:github-api:1.321'
|
||||||
api 'io.sentry:sentry:7.8.0'
|
api 'io.sentry:sentry:7.8.0'
|
||||||
api 'org.ocpsoft.prettytime:prettytime:5.0.7.Final'
|
|
||||||
api 'commons-io:commons-io:2.16.1'
|
api 'commons-io:commons-io:2.16.1'
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.0"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.0"
|
||||||
api group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.17.0"
|
api group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.17.0"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package io.xpipe.app.browser;
|
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.BrowserFileTransferMode;
|
||||||
|
import io.xpipe.app.browser.file.LocalFileSystem;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.ProcessControlProvider;
|
import io.xpipe.core.process.ProcessControlProvider;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
import io.xpipe.core.util.FailableRunnable;
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.input.ClipboardContent;
|
import javafx.scene.input.ClipboardContent;
|
||||||
import javafx.scene.input.Dragboard;
|
import javafx.scene.input.Dragboard;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
@ -45,17 +45,17 @@ public class BrowserClipboard {
|
||||||
|
|
||||||
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
|
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
|
||||||
var files =
|
var files =
|
||||||
data.stream().map(string -> string.toPath()).toList();
|
data.stream().map(f -> f.toPath()).toList();
|
||||||
if (files.size() == 0) {
|
if (files.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = new ArrayList<FileSystem.FileEntry>();
|
var entries = new ArrayList<BrowserEntry>();
|
||||||
for (Path file : files) {
|
for (Path file : files) {
|
||||||
entries.add(FileSystemHelper.getLocal(file));
|
entries.add(LocalFileSystem.getLocalBrowserEntry(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCopyClipboard.setValue(new Instance(UUID.randomUUID(), null, entries));
|
currentCopyClipboard.setValue(new Instance(UUID.randomUUID(), null, entries, BrowserFileTransferMode.COPY));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorEvent.fromThrowable(e).expected().omit().handle();
|
ErrorEvent.fromThrowable(e).expected().omit().handle();
|
||||||
}
|
}
|
||||||
|
@ -64,27 +64,27 @@ public class BrowserClipboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static ClipboardContent startDrag(FileSystem.FileEntry base, List<FileSystem.FileEntry> selected) {
|
public static ClipboardContent startDrag(FileSystem.FileEntry base, List<BrowserEntry> selected, BrowserFileTransferMode mode) {
|
||||||
if (selected.isEmpty()) {
|
if (selected.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = new ClipboardContent();
|
var content = new ClipboardContent();
|
||||||
var id = UUID.randomUUID();
|
var id = UUID.randomUUID();
|
||||||
currentDragClipboard = new Instance(id, base, new ArrayList<>(selected));
|
currentDragClipboard = new Instance(id, base, new ArrayList<>(selected), mode);
|
||||||
content.putString(currentDragClipboard.toClipboardString());
|
content.putString(currentDragClipboard.toClipboardString());
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void startCopy(FileSystem.FileEntry base, List<FileSystem.FileEntry> selected) {
|
public static void startCopy(FileSystem.FileEntry base, List<BrowserEntry> selected) {
|
||||||
if (selected.isEmpty()) {
|
if (selected.isEmpty()) {
|
||||||
currentCopyClipboard.setValue(null);
|
currentCopyClipboard.setValue(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = UUID.randomUUID();
|
var id = UUID.randomUUID();
|
||||||
currentCopyClipboard.setValue(new Instance(id, base, new ArrayList<>(selected)));
|
currentCopyClipboard.setValue(new Instance(id, base, new ArrayList<>(selected), BrowserFileTransferMode.COPY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instance retrieveCopy() {
|
public static Instance retrieveCopy() {
|
||||||
|
@ -118,11 +118,12 @@ public class BrowserClipboard {
|
||||||
public static class Instance {
|
public static class Instance {
|
||||||
UUID uuid;
|
UUID uuid;
|
||||||
FileSystem.FileEntry baseDirectory;
|
FileSystem.FileEntry baseDirectory;
|
||||||
List<FileSystem.FileEntry> entries;
|
List<BrowserEntry> entries;
|
||||||
|
BrowserFileTransferMode mode;
|
||||||
|
|
||||||
public String toClipboardString() {
|
public String toClipboardString() {
|
||||||
return entries.stream()
|
return entries.stream()
|
||||||
.map(fileEntry -> "\"" + fileEntry.getPath() + "\"")
|
.map(fileEntry -> "\"" + fileEntry.getRawFileEntry().getPath() + "\"")
|
||||||
.collect(Collectors.joining(ProcessControlProvider.get()
|
.collect(Collectors.joining(ProcessControlProvider.get()
|
||||||
.getEffectiveLocalDialect()
|
.getEffectiveLocalDialect()
|
||||||
.getNewLine()
|
.getNewLine()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.browser;
|
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.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.core.AppStyle;
|
import io.xpipe.app.core.AppStyle;
|
||||||
import io.xpipe.app.core.AppWindowHelper;
|
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.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
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.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -21,7 +18,6 @@ import javafx.scene.control.OverrunStyle;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
@ -33,14 +29,14 @@ import java.util.function.Function;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BrowserSelectionListComp extends SimpleComp {
|
public class BrowserSelectionListComp extends SimpleComp {
|
||||||
|
|
||||||
ObservableList<FileSystem.FileEntry> list;
|
ObservableList<BrowserEntry> list;
|
||||||
Function<FileSystem.FileEntry, ObservableValue<String>> nameTransformation;
|
Function<BrowserEntry, ObservableValue<String>> nameTransformation;
|
||||||
|
|
||||||
public BrowserSelectionListComp(ObservableList<FileSystem.FileEntry> list) {
|
public BrowserSelectionListComp(ObservableList<BrowserEntry> list) {
|
||||||
this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath())));
|
this(list, entry -> new SimpleStringProperty(entry.getFileName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Image snapshot(ObservableList<FileSystem.FileEntry> list) {
|
public static Image snapshot(ObservableList<BrowserEntry> list) {
|
||||||
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion();
|
||||||
var scene = new Scene(r);
|
var scene = new Scene(r);
|
||||||
AppWindowHelper.setupStylesheets(scene);
|
AppWindowHelper.setupStylesheets(scene);
|
||||||
|
@ -54,7 +50,7 @@ public class BrowserSelectionListComp extends SimpleComp {
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var c = new ListBoxViewComp<>(list, list, entry -> {
|
var c = new ListBoxViewComp<>(list, list, entry -> {
|
||||||
return Comp.of(() -> {
|
return Comp.of(() -> {
|
||||||
var image = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24)
|
var image = PrettyImageHelper.ofFixedSizeSquare(entry.getIcon(), 24)
|
||||||
.createRegion();
|
.createRegion();
|
||||||
var l = new Label(null, image);
|
var l = new Label(null, image);
|
||||||
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Spacer;
|
||||||
import io.xpipe.app.browser.file.BrowserContextMenu;
|
import io.xpipe.app.browser.file.BrowserContextMenu;
|
||||||
import io.xpipe.app.browser.file.BrowserFileListCompEntry;
|
import io.xpipe.app.browser.file.BrowserFileListCompEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
|
@ -11,16 +12,16 @@ import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.app.util.HumanReadableFormat;
|
import io.xpipe.app.util.HumanReadableFormat;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class BrowserStatusBarComp extends SimpleComp {
|
public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
@ -56,7 +57,9 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
|
var transferred = HumanReadableFormat.progressByteCount(p.getTransferred());
|
||||||
var all = HumanReadableFormat.byteCount(p.getTotal());
|
var all = HumanReadableFormat.byteCount(p.getTotal());
|
||||||
var name = (p.getName() != null ? " @ " + p.getName() + " " : "");
|
var name = (p.getName() != null ? " @ " + p.getName() + " " : "");
|
||||||
return transferred + " / " + all + name;
|
var time = p.getTotal() > 50_000_000 && p.elapsedTime().compareTo(Duration.of(200, ChronoUnit.MILLIS)) > 0 ? " | "
|
||||||
|
+ HumanReadableFormat.duration(p.expectedTimeRemaining()) : " | ...";
|
||||||
|
return transferred + " / " + all + name + time;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var progressComp = new LabelComp(text).styleClass("progress");
|
var progressComp = new LabelComp(text).styleClass("progress");
|
||||||
|
@ -87,9 +90,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||||
|
|
||||||
var allCount = Bindings.createIntegerBinding(
|
var allCount = Bindings.createIntegerBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return (int) model.getFileList().getAll().getValue().stream()
|
return model.getFileList().getAll().getValue().size();
|
||||||
.filter(entry -> !entry.isSynthetic())
|
|
||||||
.count();
|
|
||||||
},
|
},
|
||||||
model.getFileList().getAll());
|
model.getFileList().getAll());
|
||||||
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
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;
|
||||||
|
@ -9,10 +10,7 @@ import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
|
||||||
import io.xpipe.app.fxcomps.impl.*;
|
import io.xpipe.app.fxcomps.impl.*;
|
||||||
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -21,7 +19,6 @@ import javafx.scene.input.Dragboard;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.AnchorPane;
|
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;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -50,25 +47,21 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
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 = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getFileEntry());
|
var binding = ListBindingsHelper.mappedContentBinding(syncItems, item -> item.getBrowserEntry());
|
||||||
var list = new BrowserSelectionListComp(
|
var list = new BrowserSelectionListComp(
|
||||||
binding,
|
binding,
|
||||||
entry -> Bindings.createStringBinding(
|
entry -> Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
var sourceItem = syncItems.stream()
|
var sourceItem = syncItems.stream()
|
||||||
.filter(item -> item.getFileEntry() == entry)
|
.filter(item -> item.getBrowserEntry() == entry)
|
||||||
.findAny();
|
.findAny();
|
||||||
if (sourceItem.isEmpty()) {
|
if (sourceItem.isEmpty()) {
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
var name =
|
var name = entry.getModel() == null || sourceItem.get().downloadFinished().get()
|
||||||
sourceItem.get().downloadFinished().get()
|
|
||||||
? "Local"
|
? "Local"
|
||||||
: DataStorage.get()
|
: entry.getModel().getFileSystemModel().getName();
|
||||||
.getStoreDisplayName(entry.getFileSystem()
|
return entry.getFileName() + " (" + name + ")";
|
||||||
.getStore())
|
|
||||||
.orElse("?");
|
|
||||||
return FileNames.getFileName(entry.getPath()) + " (" + name + ")";
|
|
||||||
},
|
},
|
||||||
syncAllDownloaded))
|
syncAllDownloaded))
|
||||||
.apply(struc -> struc.get().setMinHeight(150))
|
.apply(struc -> struc.get().setMinHeight(150))
|
||||||
|
@ -154,11 +147,11 @@ public class BrowserTransferComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
var selected = syncItems.stream()
|
var selected = syncItems.stream()
|
||||||
.map(BrowserTransferModel.Item::getFileEntry)
|
.map(item -> item.getBrowserEntry())
|
||||||
.toList();
|
.toList();
|
||||||
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY);
|
||||||
|
|
||||||
var cc = BrowserClipboard.startDrag(null, selected);
|
var cc = BrowserClipboard.startDrag(null, selected, BrowserFileTransferMode.NORMAL);
|
||||||
if (cc == null) {
|
if (cc == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package io.xpipe.app.browser;
|
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.BrowserFileTransferMode;
|
||||||
|
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.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ShellTemp;
|
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.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
@ -17,7 +17,6 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
@ -66,9 +65,9 @@ public class BrowserTransferModel {
|
||||||
items.clear();
|
items.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drop(OpenFileSystemModel model, List<FileSystem.FileEntry> entries) {
|
public void drop(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
entries.forEach(entry -> {
|
entries.forEach(entry -> {
|
||||||
var name = FileNames.getFileName(entry.getPath());
|
var name = entry.getFileName();
|
||||||
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -89,14 +88,14 @@ public class BrowserTransferModel {
|
||||||
try {
|
try {
|
||||||
var paths = entries.stream().map(File::toPath).filter(Files::exists).toList();
|
var paths = entries.stream().map(File::toPath).filter(Files::exists).toList();
|
||||||
for (Path path : paths) {
|
for (Path path : paths) {
|
||||||
var entry = FileSystemHelper.getLocal(path);
|
var entry = LocalFileSystem.getLocalBrowserEntry(path);
|
||||||
var name = entry.getName();
|
var name = entry.getFileName();
|
||||||
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
if (items.stream().anyMatch(item -> item.getName().equals(name))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = new Item(null, name, entry, path);
|
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);
|
items.add(item);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -127,16 +126,17 @@ public class BrowserTransferModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try (var b = new BooleanScope(downloading).start()) {
|
try (var ignored = new BooleanScope(downloading).start()) {
|
||||||
FileSystemHelper.dropFilesInto(
|
var op = new BrowserFileTransferOperation(
|
||||||
FileSystemHelper.getLocal(TEMP),
|
LocalFileSystem.getLocalFileEntry(TEMP),
|
||||||
List.of(item.getFileEntry()),
|
List.of(item.getBrowserEntry().getRawFileEntry()),
|
||||||
true,
|
BrowserFileTransferMode.COPY,
|
||||||
false,
|
false,
|
||||||
progress -> {
|
progress -> {
|
||||||
item.getProgress().setValue(progress);
|
item.getProgress().setValue(progress);
|
||||||
item.getOpenFileSystemModel().getProgress().setValue(progress);
|
item.getOpenFileSystemModel().getProgress().setValue(progress);
|
||||||
});
|
});
|
||||||
|
op.execute();
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ErrorEvent.fromThrowable(t).handle();
|
ErrorEvent.fromThrowable(t).handle();
|
||||||
|
@ -151,15 +151,15 @@ public class BrowserTransferModel {
|
||||||
public static class Item {
|
public static class Item {
|
||||||
OpenFileSystemModel openFileSystemModel;
|
OpenFileSystemModel openFileSystemModel;
|
||||||
String name;
|
String name;
|
||||||
FileSystem.FileEntry fileEntry;
|
BrowserEntry browserEntry;
|
||||||
Path localFile;
|
Path localFile;
|
||||||
Property<BrowserTransferProgress> progress;
|
Property<BrowserTransferProgress> progress;
|
||||||
|
|
||||||
public Item(
|
public Item(
|
||||||
OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) {
|
OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) {
|
||||||
this.openFileSystemModel = openFileSystemModel;
|
this.openFileSystemModel = openFileSystemModel;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.fileEntry = fileEntry;
|
this.browserEntry = browserEntry;
|
||||||
this.localFile = localFile;
|
this.localFile = localFile;
|
||||||
this.progress = new SimpleObjectProperty<>();
|
this.progress = new SimpleObjectProperty<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,45 @@ package io.xpipe.app.browser;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public class BrowserTransferProgress {
|
public class BrowserTransferProgress {
|
||||||
|
|
||||||
String name;
|
String name;
|
||||||
long transferred;
|
long transferred;
|
||||||
long total;
|
long total;
|
||||||
|
Instant start;
|
||||||
|
|
||||||
public static BrowserTransferProgress empty() {
|
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) {
|
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) {
|
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() {
|
public boolean done() {
|
||||||
return transferred >= total;
|
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) / share;
|
||||||
|
var restMillis = (long) (elapsed.toMillis() * rest);
|
||||||
|
return Duration.of(restMillis, ChronoUnit.MILLIS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,7 @@ public final class BrowserContextMenu extends ContextMenu {
|
||||||
? selected.stream()
|
? selected.stream()
|
||||||
.map(browserEntry -> new BrowserEntry(
|
.map(browserEntry -> new BrowserEntry(
|
||||||
browserEntry.getRawFileEntry().resolved(),
|
browserEntry.getRawFileEntry().resolved(),
|
||||||
browserEntry.getModel(),
|
browserEntry.getModel()))
|
||||||
browserEntry.isSynthetic()))
|
|
||||||
.toList()
|
.toList()
|
||||||
: selected;
|
: selected;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +43,7 @@ public final class BrowserContextMenu extends ContextMenu {
|
||||||
var empty = source == null;
|
var empty = source == null;
|
||||||
var selected = new ArrayList<>(
|
var selected = new ArrayList<>(
|
||||||
empty
|
empty
|
||||||
? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList(), false))
|
? List.of(new BrowserEntry(model.getCurrentDirectory(), model.getFileList()))
|
||||||
: model.getFileList().getSelection());
|
: model.getFileList().getSelection());
|
||||||
if (source != null && !selected.contains(source)) {
|
if (source != null && !selected.contains(source)) {
|
||||||
selected.add(source);
|
selected.add(source);
|
||||||
|
|
|
@ -13,14 +13,12 @@ public class BrowserEntry {
|
||||||
|
|
||||||
private final BrowserFileListModel model;
|
private final BrowserFileListModel model;
|
||||||
private final FileSystem.FileEntry rawFileEntry;
|
private final FileSystem.FileEntry rawFileEntry;
|
||||||
private final boolean synthetic;
|
|
||||||
private final BrowserIconFileType fileType;
|
private final BrowserIconFileType fileType;
|
||||||
private final BrowserIconDirectoryType directoryType;
|
private final BrowserIconDirectoryType directoryType;
|
||||||
|
|
||||||
public BrowserEntry(FileSystem.FileEntry rawFileEntry, BrowserFileListModel model, boolean synthetic) {
|
public BrowserEntry(FileSystem.FileEntry rawFileEntry, BrowserFileListModel model) {
|
||||||
this.rawFileEntry = rawFileEntry;
|
this.rawFileEntry = rawFileEntry;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.synthetic = synthetic;
|
|
||||||
this.fileType = fileType(rawFileEntry);
|
this.fileType = fileType(rawFileEntry);
|
||||||
this.directoryType = directoryType(rawFileEntry);
|
this.directoryType = directoryType(rawFileEntry);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +50,17 @@ public class BrowserEntry {
|
||||||
|
|
||||||
return null;
|
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() {
|
public String getFileName() {
|
||||||
return getRawFileEntry().getName();
|
return getRawFileEntry().getName();
|
||||||
|
@ -61,9 +70,4 @@ public class BrowserEntry {
|
||||||
var n = getFileName();
|
var n = getFileName();
|
||||||
return FileNames.quoteIfNecessary(n);
|
return FileNames.quoteIfNecessary(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOptionallyQuotedFilePath() {
|
|
||||||
var n = rawFileEntry.getPath();
|
|
||||||
return FileNames.quoteIfNecessary(n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package io.xpipe.app.browser.file;
|
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.action.BrowserAction;
|
||||||
import io.xpipe.app.browser.icon.FileIconManager;
|
|
||||||
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
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.FileKind;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
import io.xpipe.core.store.FileSystem;
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
@ -31,16 +31,11 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.skin.TableViewSkin;
|
import javafx.scene.control.skin.TableViewSkin;
|
||||||
import javafx.scene.control.skin.VirtualFlow;
|
import javafx.scene.control.skin.VirtualFlow;
|
||||||
import javafx.scene.input.DragEvent;
|
import javafx.scene.input.*;
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
|
||||||
import atlantafx.base.theme.Styles;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -82,7 +77,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
: null));
|
: null));
|
||||||
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
|
filenameCol.setComparator(Comparator.comparing(String::toLowerCase));
|
||||||
filenameCol.setSortType(ASCENDING);
|
filenameCol.setSortType(ASCENDING);
|
||||||
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing()));
|
filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing(), col.getTableView()));
|
||||||
|
|
||||||
var sizeCol = new TableColumn<BrowserEntry, Number>();
|
var sizeCol = new TableColumn<BrowserEntry, Number>();
|
||||||
sizeCol.textProperty().bind(AppI18n.observable("size"));
|
sizeCol.textProperty().bind(AppI18n.observable("size"));
|
||||||
|
@ -137,29 +132,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
table.getSelectionModel().setCellSelectionEnabled(false);
|
table.getSelectionModel().setCellSelectionEnabled(false);
|
||||||
|
|
||||||
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
var toSelect = new ArrayList<>(c.getList());
|
fileList.getSelection().setAll(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().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
|
@ -174,7 +147,6 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
var indices = c.getList().stream()
|
var indices = c.getList().stream()
|
||||||
.skip(1)
|
|
||||||
.mapToInt(entry -> table.getItems().indexOf(entry))
|
.mapToInt(entry -> table.getItems().indexOf(entry))
|
||||||
.toArray();
|
.toArray();
|
||||||
table.getSelectionModel()
|
table.getSelectionModel()
|
||||||
|
@ -196,8 +168,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
browserAction.execute(fileList.getFileSystemModel(), selected);
|
browserAction.execute(fileList.getFileSystemModel(), selected);
|
||||||
});
|
});
|
||||||
|
event.consume();
|
||||||
});
|
});
|
||||||
event.consume();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,10 +248,6 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
() -> {
|
() -> {
|
||||||
if (row.getItem() != null && row.getItem().isSynthetic()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem());
|
return new BrowserContextMenu(fileList.getFileSystemModel(), row.getItem());
|
||||||
})
|
})
|
||||||
.augment(new SimpleCompStructure<>(row));
|
.augment(new SimpleCompStructure<>(row));
|
||||||
|
@ -348,12 +316,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
TableColumn<BrowserEntry, String> modeCol) {
|
TableColumn<BrowserEntry, String> modeCol) {
|
||||||
var lastDir = new SimpleObjectProperty<FileSystem.FileEntry>();
|
var lastDir = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||||
Runnable updateHandler = () -> {
|
Runnable updateHandler = () -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
Platform.runLater(() -> {
|
||||||
var newItems = new ArrayList<>(fileList.getShown().getValue());
|
var newItems = new ArrayList<>(fileList.getShown().getValue());
|
||||||
|
|
||||||
var hasModifiedDate = newItems.size() == 0
|
var hasModifiedDate = newItems.size() == 0 || newItems.stream().anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
|
||||||
|| newItems.stream()
|
|
||||||
.anyMatch(entry -> entry.getRawFileEntry().getDate() != null);
|
|
||||||
if (!hasModifiedDate) {
|
if (!hasModifiedDate) {
|
||||||
table.getColumns().remove(mtimeCol);
|
table.getColumns().remove(mtimeCol);
|
||||||
} else {
|
} else {
|
||||||
|
@ -363,10 +329,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileList.getFileSystemModel().getFileSystem() != null) {
|
if (fileList.getFileSystemModel().getFileSystem() != null) {
|
||||||
var shell = fileList.getFileSystemModel()
|
var shell = fileList.getFileSystemModel().getFileSystem().getShell().orElseThrow();
|
||||||
.getFileSystem()
|
|
||||||
.getShell()
|
|
||||||
.orElseThrow();
|
|
||||||
var hasAttributes = !OsType.WINDOWS.equals(shell.getOsType());
|
var hasAttributes = !OsType.WINDOWS.equals(shell.getOsType());
|
||||||
if (!hasAttributes) {
|
if (!hasAttributes) {
|
||||||
table.getColumns().remove(modeCol);
|
table.getColumns().remove(modeCol);
|
||||||
|
@ -388,10 +351,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
if (!Objects.equals(lastDir.get(), currentDirectory)) {
|
if (!Objects.equals(lastDir.get(), currentDirectory)) {
|
||||||
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
|
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
|
||||||
if (skin != null) {
|
if (skin != null) {
|
||||||
VirtualFlow<?> flow =
|
VirtualFlow<?> flow = (VirtualFlow<?>) skin.getChildren().get(1);
|
||||||
(VirtualFlow<?>) skin.getChildren().get(1);
|
ScrollBar vbar = (ScrollBar) flow.getChildrenUnmodifiable().get(2);
|
||||||
ScrollBar vbar =
|
|
||||||
(ScrollBar) flow.getChildrenUnmodifiable().get(2);
|
|
||||||
if (vbar.getValue() != 0.0) {
|
if (vbar.getValue() != 0.0) {
|
||||||
table.scrollTo(0);
|
table.scrollTo(0);
|
||||||
}
|
}
|
||||||
|
@ -496,7 +457,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
|
|
||||||
private final BooleanProperty updating = new SimpleBooleanProperty();
|
private final BooleanProperty updating = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public FilenameCell(Property<BrowserEntry> editing) {
|
public FilenameCell(Property<BrowserEntry> editing, TableView<BrowserEntry> tableView) {
|
||||||
accessibleTextProperty()
|
accessibleTextProperty()
|
||||||
.bind(Bindings.createStringBinding(
|
.bind(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -526,6 +487,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
itemProperty())
|
itemProperty())
|
||||||
.not()
|
.not()
|
||||||
.not())
|
.not())
|
||||||
|
.focusTraversable(false)
|
||||||
|
.apply(struc -> struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
getTableRow().requestFocus();
|
||||||
|
}))
|
||||||
.createRegion();
|
.createRegion();
|
||||||
|
|
||||||
editing.addListener((observable, oldValue, newValue) -> {
|
editing.addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -554,6 +519,16 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||||
graphic.setAlignment(Pos.CENTER_LEFT);
|
graphic.setAlignment(Pos.CENTER_LEFT);
|
||||||
setGraphic(graphic);
|
setGraphic(graphic);
|
||||||
|
|
||||||
|
tableView.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||||
|
if (event.getCode() == KeyCode.RIGHT) {
|
||||||
|
var selected = fileList.getSelection();
|
||||||
|
if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) {
|
||||||
|
((ButtonBase) quickAccess).fire();
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -573,15 +548,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
// Visibility seems to be bugged, so use opacity
|
// Visibility seems to be bugged, so use opacity
|
||||||
setOpacity(0.0);
|
setOpacity(0.0);
|
||||||
} else {
|
} else {
|
||||||
var isParentLink = getTableRow()
|
img.set(getTableRow().getItem().getIcon());
|
||||||
.getItem()
|
|
||||||
.getRawFileEntry()
|
|
||||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
|
||||||
img.set(FileIconManager.getFileIcon(
|
|
||||||
isParentLink
|
|
||||||
? fileList.getFileSystemModel().getCurrentDirectory()
|
|
||||||
: getTableRow().getItem().getRawFileEntry(),
|
|
||||||
isParentLink));
|
|
||||||
|
|
||||||
var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY;
|
var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY;
|
||||||
pseudoClassStateChanged(FOLDER, isDirectory);
|
pseudoClassStateChanged(FOLDER, isDirectory);
|
||||||
|
@ -594,9 +561,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
.resolved()
|
.resolved()
|
||||||
.getPath()
|
.getPath()
|
||||||
: getTableRow().getItem().getFileName();
|
: getTableRow().getItem().getFileName();
|
||||||
var fileName = isParentLink ? ".." : normalName;
|
var fileName = normalName;
|
||||||
var hidden = !isParentLink
|
var hidden = getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith(".");
|
||||||
&& (getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith("."));
|
|
||||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||||
text.set(fileName);
|
text.set(fileName);
|
||||||
// Visibility seems to be bugged, so use opacity
|
// Visibility seems to be bugged, so use opacity
|
||||||
|
|
|
@ -3,13 +3,11 @@ package io.xpipe.app.browser.file;
|
||||||
import io.xpipe.app.browser.BrowserClipboard;
|
import io.xpipe.app.browser.BrowserClipboard;
|
||||||
import io.xpipe.app.browser.BrowserSelectionListComp;
|
import io.xpipe.app.browser.BrowserSelectionListComp;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.input.*;
|
import javafx.scene.input.*;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -44,11 +42,13 @@ public class BrowserFileListCompEntry {
|
||||||
// Only clear for normal clicks
|
// Only clear for normal clicks
|
||||||
if (t.isStillSincePress()) {
|
if (t.isStillSincePress()) {
|
||||||
model.getSelection().clear();
|
model.getSelection().clear();
|
||||||
|
tv.requestFocus();
|
||||||
}
|
}
|
||||||
t.consume();
|
t.consume();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row.requestFocus();
|
||||||
if (t.getClickCount() == 2 && t.getButton() == MouseButton.PRIMARY) {
|
if (t.getClickCount() == 2 && t.getButton() == MouseButton.PRIMARY) {
|
||||||
model.onDoubleClick(item);
|
model.onDoubleClick(item);
|
||||||
t.consume();
|
t.consume();
|
||||||
|
@ -58,7 +58,7 @@ public class BrowserFileListCompEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMouseShiftClick(MouseEvent t) {
|
public void onMouseShiftClick(MouseEvent t) {
|
||||||
if (isSynthetic()) {
|
if (t.getButton() != MouseButton.PRIMARY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +87,6 @@ public class BrowserFileListCompEntry {
|
||||||
t.consume();
|
t.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSynthetic() {
|
|
||||||
return item != null
|
|
||||||
&& item.getRawFileEntry().equals(model.getFileSystemModel().getCurrentParentDirectory());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean acceptsDrop(DragEvent event) {
|
private boolean acceptsDrop(DragEvent event) {
|
||||||
// Accept drops from outside the app window
|
// Accept drops from outside the app window
|
||||||
if (event.getGestureSource() == null) {
|
if (event.getGestureSource() == null) {
|
||||||
|
@ -109,7 +104,7 @@ public class BrowserFileListCompEntry {
|
||||||
|
|
||||||
if (!Objects.equals(
|
if (!Objects.equals(
|
||||||
model.getFileSystemModel().getFileSystem(),
|
model.getFileSystemModel().getFileSystem(),
|
||||||
cb.getEntries().getFirst().getFileSystem())) {
|
cb.getEntries().getFirst().getRawFileEntry().getFileSystem())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +118,7 @@ public class BrowserFileListCompEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent dropping items onto themselves
|
// Prevent dropping items onto themselves
|
||||||
if (item != null && cb.getEntries().contains(item.getRawFileEntry())) {
|
if (item != null && cb.getEntries().contains(item)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +152,7 @@ public class BrowserFileListCompEntry {
|
||||||
var target = item != null && item.getRawFileEntry().getKind() == FileKind.DIRECTORY
|
var target = item != null && item.getRawFileEntry().getKind() == FileKind.DIRECTORY
|
||||||
? item.getRawFileEntry()
|
? item.getRawFileEntry()
|
||||||
: model.getFileSystemModel().getCurrentDirectory();
|
: model.getFileSystemModel().getCurrentDirectory();
|
||||||
model.getFileSystemModel().dropFilesIntoAsync(target, files, false);
|
model.getFileSystemModel().dropFilesIntoAsync(target, files.stream().map(browserEntry -> browserEntry.getRawFileEntry()).toList(), db.getMode());
|
||||||
event.setDropCompleted(true);
|
event.setDropCompleted(true);
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
|
@ -174,17 +169,16 @@ public class BrowserFileListCompEntry {
|
||||||
|
|
||||||
public void startDrag(MouseEvent event) {
|
public void startDrag(MouseEvent event) {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
row.startFullDrag();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSynthetic()) {
|
if (event.getButton() != MouseButton.PRIMARY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var selected = model.getSelectedRaw();
|
var selected = model.getSelection();
|
||||||
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
|
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
|
||||||
db.setContent(BrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected));
|
db.setContent(BrowserClipboard.startDrag(model.getFileSystemModel().getCurrentDirectory(), selected, event.isAltDown() ? BrowserFileTransferMode.MOVE : BrowserFileTransferMode.NORMAL));
|
||||||
|
|
||||||
Image image = BrowserSelectionListComp.snapshot(selected);
|
Image image = BrowserSelectionListComp.snapshot(selected);
|
||||||
db.setDragView(image, -20, 15);
|
db.setDragView(image, -20, 15);
|
||||||
|
@ -224,7 +218,7 @@ public class BrowserFileListCompEntry {
|
||||||
model.getFileSystemModel().cdAsync(item.getRawFileEntry().getPath());
|
model.getFileSystemModel().cdAsync(item.getRawFileEntry().getPath());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DROP_TIMER.schedule(activeTask, 1000);
|
DROP_TIMER.schedule(activeTask, 1200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDragEntered(DragEvent event) {
|
public void onDragEntered(DragEvent event) {
|
||||||
|
@ -244,7 +238,7 @@ public class BrowserFileListCompEntry {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item == null || item.isSynthetic()) {
|
if (item == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,10 +59,7 @@ public final class BrowserFileListModel {
|
||||||
|
|
||||||
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
|
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
|
||||||
try (var s = newFiles) {
|
try (var s = newFiles) {
|
||||||
var parent = fileSystemModel.getCurrentParentDirectory();
|
var l = s.filter(entry -> entry != null).map(entry -> new BrowserEntry(entry, this))
|
||||||
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)))
|
|
||||||
.toList();
|
.toList();
|
||||||
all.setValue(l);
|
all.setValue(l);
|
||||||
refreshShown();
|
refreshShown();
|
||||||
|
@ -94,14 +91,13 @@ public final class BrowserFileListModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Comparator<BrowserEntry> order() {
|
public Comparator<BrowserEntry> order() {
|
||||||
var syntheticFirst = Comparator.<BrowserEntry, Boolean>comparing(path -> !path.isSynthetic());
|
|
||||||
var dirsFirst = Comparator.<BrowserEntry, Boolean>comparing(
|
var dirsFirst = Comparator.<BrowserEntry, Boolean>comparing(
|
||||||
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY);
|
||||||
var comp = comparatorProperty.getValue();
|
var comp = comparatorProperty.getValue();
|
||||||
|
|
||||||
Comparator<BrowserEntry> us = comp != null
|
Comparator<BrowserEntry> us = comp != null
|
||||||
? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp)
|
? dirsFirst.thenComparing(comp)
|
||||||
: syntheticFirst.thenComparing(dirsFirst);
|
: dirsFirst;
|
||||||
return us;
|
return us;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class BrowserFileOverviewComp extends SimpleComp {
|
||||||
var graphic = new HorizontalComp(List.of(
|
var graphic = new HorizontalComp(List.of(
|
||||||
icon,
|
icon,
|
||||||
new BrowserQuickAccessButtonComp(
|
new BrowserQuickAccessButtonComp(
|
||||||
() -> new BrowserEntry(entry, model.getFileList(), false), model)));
|
() -> new BrowserEntry(entry, model.getFileList()), model)));
|
||||||
var l = new Button(entry.getPath(), graphic.createRegion());
|
var l = new Button(entry.getPath(), graphic.createRegion());
|
||||||
l.setGraphicTextGap(1);
|
l.setGraphicTextGap(1);
|
||||||
l.setOnAction(event -> {
|
l.setOnAction(event -> {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
|
public enum BrowserFileTransferMode {
|
||||||
|
|
||||||
|
NORMAL,
|
||||||
|
COPY,
|
||||||
|
MOVE
|
||||||
|
}
|
|
@ -0,0 +1,300 @@
|
||||||
|
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<FileSystem.FileEntry> files;
|
||||||
|
private final BrowserFileTransferMode transferMode;
|
||||||
|
private final boolean checkConflicts;
|
||||||
|
private final Consumer<BrowserTransferProgress> progress;
|
||||||
|
|
||||||
|
BrowserAlerts.FileConflictChoice lastConflictChoice;
|
||||||
|
|
||||||
|
public BrowserFileTransferOperation(FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode transferMode, boolean checkConflicts,
|
||||||
|
Consumer<BrowserTransferProgress> progress
|
||||||
|
) {
|
||||||
|
this.target = target;
|
||||||
|
this.files = files;
|
||||||
|
this.transferMode = transferMode;
|
||||||
|
this.checkConflicts = checkConflicts;
|
||||||
|
this.progress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BrowserFileTransferOperation ofLocal(FileSystem.FileEntry target, List<Path> files, BrowserFileTransferMode transferMode, boolean checkConflicts, Consumer<BrowserTransferProgress> 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, transferMode, 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());
|
||||||
|
var doesMove = transferMode == BrowserFileTransferMode.MOVE || (same && transferMode == BrowserFileTransferMode.NORMAL);
|
||||||
|
if (doesMove) {
|
||||||
|
if (!BrowserAlerts.showMoveAlert(files, target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var file : files) {
|
||||||
|
if (same) {
|
||||||
|
handleSingleOnSameFileSystem(file);
|
||||||
|
updateProgress(BrowserTransferProgress.finished(file.getName(), file.getSize()));
|
||||||
|
} else {
|
||||||
|
handleSingleAcrossFileSystems(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!same && doesMove) {
|
||||||
|
for (var file : files) {
|
||||||
|
deleteSingle(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var doesMove = transferMode == BrowserFileTransferMode.MOVE || transferMode == BrowserFileTransferMode.NORMAL;
|
||||||
|
if (doesMove) {
|
||||||
|
target.getFileSystem().move(sourceFile, targetFile);
|
||||||
|
} else {
|
||||||
|
target.getFileSystem().copy(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<FileSystem.FileEntry, String>();
|
||||||
|
|
||||||
|
// 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<FileSystem.FileEntry> 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 void deleteSingle(FileSystem.FileEntry source) throws Exception {
|
||||||
|
source.getFileSystem().delete(source.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,7 +101,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||||
newItems.add(empty);
|
newItems.add(empty);
|
||||||
} else {
|
} else {
|
||||||
var browserEntries = list.stream()
|
var browserEntries = list.stream()
|
||||||
.map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList(), false))
|
.map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList()))
|
||||||
.toList();
|
.toList();
|
||||||
var menus = browserEntries.stream()
|
var menus = browserEntries.stream()
|
||||||
.sorted(model.getFileList().order())
|
.sorted(model.getFileList().order())
|
||||||
|
|
|
@ -1,28 +1,17 @@
|
||||||
package io.xpipe.app.browser.file;
|
package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserTransferProgress;
|
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.core.process.OsType;
|
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.time.Instant;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class FileSystemHelper {
|
public class FileSystemHelper {
|
||||||
|
|
||||||
private static final int DEFAULT_BUFFER_SIZE = 1024;
|
|
||||||
private static FileSystem localFileSystem;
|
|
||||||
|
|
||||||
public static String adjustPath(OpenFileSystemModel model, String path) {
|
public static String adjustPath(OpenFileSystemModel model, String path) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return 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 {
|
public static FileSystem.FileEntry getRemoteWrapper(FileSystem fileSystem, String file) throws Exception {
|
||||||
return new FileSystem.FileEntry(
|
return new FileSystem.FileEntry(
|
||||||
fileSystem,
|
fileSystem,
|
||||||
|
@ -163,24 +135,6 @@ public class FileSystemHelper {
|
||||||
fileSystem.directoryExists(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
fileSystem.directoryExists(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dropLocalFilesInto(
|
|
||||||
FileSystem.FileEntry entry,
|
|
||||||
List<Path> files,
|
|
||||||
Consumer<BrowserTransferProgress> 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<FileSystem.FileEntry> files) {
|
public static void delete(List<FileSystem.FileEntry> files) {
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -194,255 +148,4 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dropFilesInto(
|
|
||||||
FileSystem.FileEntry target,
|
|
||||||
List<FileSystem.FileEntry> files,
|
|
||||||
boolean explicitCopy,
|
|
||||||
boolean checkConflicts,
|
|
||||||
Consumer<BrowserTransferProgress> 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<BrowserAlerts.FileConflictChoice> 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<BrowserAlerts.FileConflictChoice> 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<BrowserTransferProgress> progress,
|
|
||||||
AtomicReference<BrowserAlerts.FileConflictChoice> 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<FileSystem.FileEntry, String>();
|
|
||||||
|
|
||||||
// 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<FileSystem.FileEntry> 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<BrowserAlerts.FileConflictChoice> 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<BrowserTransferProgress> 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import io.xpipe.app.browser.BrowserSavedState;
|
||||||
import io.xpipe.app.browser.BrowserTransferProgress;
|
import io.xpipe.app.browser.BrowserTransferProgress;
|
||||||
import io.xpipe.app.browser.action.BrowserAction;
|
import io.xpipe.app.browser.action.BrowserAction;
|
||||||
import io.xpipe.app.browser.file.BrowserFileListModel;
|
import io.xpipe.app.browser.file.BrowserFileListModel;
|
||||||
|
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||||
|
import io.xpipe.app.browser.file.BrowserFileTransferOperation;
|
||||||
import io.xpipe.app.browser.file.FileSystemHelper;
|
import io.xpipe.app.browser.file.FileSystemHelper;
|
||||||
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||||
|
@ -22,10 +24,8 @@ import io.xpipe.core.process.ShellDialects;
|
||||||
import io.xpipe.core.process.ShellOpenFunction;
|
import io.xpipe.core.process.ShellOpenFunction;
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
import io.xpipe.core.util.FailableConsumer;
|
import io.xpipe.core.util.FailableConsumer;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
@ -343,14 +343,16 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
FileSystemHelper.dropLocalFilesInto(entry, files, progress::setValue, true);
|
var op = BrowserFileTransferOperation.ofLocal(entry, files,BrowserFileTransferMode.COPY,true, progress::setValue);
|
||||||
|
op.execute();
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dropFilesIntoAsync(
|
public void dropFilesIntoAsync(
|
||||||
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, boolean explicitCopy) {
|
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode mode
|
||||||
|
) {
|
||||||
// We don't have to do anything in this case
|
// We don't have to do anything in this case
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -363,9 +365,8 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
FileSystemHelper.dropFilesInto(target, files, explicitCopy, true, browserTransferProgress -> {
|
var op = new BrowserFileTransferOperation(target, files, mode,true, progress::setValue);
|
||||||
progress.setValue(browserTransferProgress);
|
op.execute();
|
||||||
});
|
|
||||||
refreshSync();
|
refreshSync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -389,7 +389,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||||
.forEach(storeCategoryWrapper -> {
|
.forEach(storeCategoryWrapper -> {
|
||||||
MenuItem m = new MenuItem();
|
MenuItem m = new MenuItem();
|
||||||
m.textProperty().bind(storeCategoryWrapper.nameProperty());
|
m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue());
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||||
event.consume();
|
event.consume();
|
||||||
|
|
|
@ -6,21 +6,17 @@ import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.prefs.SupportedLocale;
|
|
||||||
import io.xpipe.app.util.OptionsBuilder;
|
import io.xpipe.app.util.OptionsBuilder;
|
||||||
import io.xpipe.app.util.Translatable;
|
import io.xpipe.app.util.Translatable;
|
||||||
import io.xpipe.core.util.ModuleHelper;
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
import io.xpipe.core.util.XPipeInstallation;
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.ocpsoft.prettytime.PrettyTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -131,7 +127,7 @@ public class AppI18n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadedTranslations getLoaded() {
|
public LoadedTranslations getLoaded() {
|
||||||
return currentLanguage.getValue() != null ? currentLanguage.getValue() : english;
|
return currentLanguage.getValue() != null ? currentLanguage.getValue() : english;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,21 +274,15 @@ public class AppI18n {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var prettyTime = new PrettyTime(
|
return new LoadedTranslations(l, translations, markdownDocumentations);
|
||||||
AppPrefs.get() != null
|
|
||||||
? AppPrefs.get().language().getValue().getLocale()
|
|
||||||
: SupportedLocale.getEnglish().getLocale());
|
|
||||||
|
|
||||||
return new LoadedTranslations(l, translations, markdownDocumentations, prettyTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
static class LoadedTranslations {
|
public static class LoadedTranslations {
|
||||||
|
|
||||||
Locale locale;
|
Locale locale;
|
||||||
Map<String, String> translations;
|
Map<String, String> translations;
|
||||||
Map<String, String> markdownDocumentations;
|
Map<String, String> markdownDocumentations;
|
||||||
PrettyTime prettyTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.core.mode;
|
package io.xpipe.app.core.mode;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.file.LocalFileSystem;
|
||||||
import io.xpipe.app.browser.icon.FileIconManager;
|
import io.xpipe.app.browser.icon.FileIconManager;
|
||||||
import io.xpipe.app.core.App;
|
import io.xpipe.app.core.App;
|
||||||
import io.xpipe.app.core.AppGreetings;
|
import io.xpipe.app.core.AppGreetings;
|
||||||
|
@ -49,9 +50,13 @@ public class GuiMode extends PlatformMode {
|
||||||
});
|
});
|
||||||
TrackEvent.info("Window setup complete");
|
TrackEvent.info("Window setup complete");
|
||||||
|
|
||||||
ThreadHelper.runAsync(() -> {
|
// Can be loaded async
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
FileIconManager.loadIfNecessary();
|
FileIconManager.loadIfNecessary();
|
||||||
});
|
});
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
LocalFileSystem.init();
|
||||||
|
});
|
||||||
|
|
||||||
UpdateChangelogAlert.showIfNeeded();
|
UpdateChangelogAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,6 @@ public abstract class DataStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void dispose() {
|
private synchronized void dispose() {
|
||||||
onReset();
|
|
||||||
save(true);
|
save(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,8 +182,6 @@ public abstract class DataStorage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onReset() {}
|
|
||||||
|
|
||||||
protected Path getStoresDir() {
|
protected Path getStoresDir() {
|
||||||
return dir.resolve("stores");
|
return dir.resolve("stores");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.util;
|
||||||
|
|
||||||
import java.text.CharacterIterator;
|
import java.text.CharacterIterator;
|
||||||
import java.text.StringCharacterIterator;
|
import java.text.StringCharacterIterator;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
@ -80,4 +81,13 @@ public final class HumanReadableFormat {
|
||||||
private static int getWeekNumber(LocalDateTime date) {
|
private static int getWeekNumber(LocalDateTime date) {
|
||||||
return date.get(WeekFields.of(Locale.getDefault()).weekOfYear());
|
return date.get(WeekFields.of(Locale.getDefault()).weekOfYear());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String duration(Duration duration) {
|
||||||
|
return duration.toString()
|
||||||
|
.substring(2)
|
||||||
|
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
|
||||||
|
.replaceAll("\\.\\d+", "")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ open module io.xpipe.app {
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
requires org.slf4j.jdk.platform.logging;
|
requires org.slf4j.jdk.platform.logging;
|
||||||
requires atlantafx.base;
|
requires atlantafx.base;
|
||||||
requires org.ocpsoft.prettytime;
|
|
||||||
requires com.vladsch.flexmark;
|
requires com.vladsch.flexmark;
|
||||||
requires com.fasterxml.jackson.core;
|
requires com.fasterxml.jackson.core;
|
||||||
requires com.fasterxml.jackson.databind;
|
requires com.fasterxml.jackson.databind;
|
||||||
|
|
|
@ -103,7 +103,6 @@ project.ext {
|
||||||
authors = 'Christopher Schnick'
|
authors = 'Christopher Schnick'
|
||||||
javafxVersion = '22.0.1'
|
javafxVersion = '22.0.1'
|
||||||
platformName = getPlatformName()
|
platformName = getPlatformName()
|
||||||
artifactChecksums = new HashMap<String, String>()
|
|
||||||
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
||||||
jvmRunArgs = [
|
jvmRunArgs = [
|
||||||
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class CopyAction implements LeafAction {
|
||||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
BrowserClipboard.startCopy(
|
BrowserClipboard.startCopy(
|
||||||
model.getCurrentDirectory(),
|
model.getCurrentDirectory(),
|
||||||
entries.stream().map(entry -> entry.getRawFileEntry()).toList());
|
entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.ext.base.browser;
|
||||||
import io.xpipe.app.browser.BrowserClipboard;
|
import io.xpipe.app.browser.BrowserClipboard;
|
||||||
import io.xpipe.app.browser.action.LeafAction;
|
import io.xpipe.app.browser.action.LeafAction;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
|
import io.xpipe.app.browser.file.BrowserFileTransferMode;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
@ -34,7 +35,7 @@ public class PasteAction implements LeafAction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.dropFilesIntoAsync(target, files, true);
|
model.dropFilesIntoAsync(target, files.stream().map(browserEntry -> browserEntry.getRawFileEntry()).toList(), BrowserFileTransferMode.COPY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue