mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10:23 +00:00
Show owner information for files
This commit is contained in:
parent
1ebc60cb71
commit
ef5427f046
39 changed files with 344 additions and 199 deletions
|
@ -6,7 +6,7 @@ import io.xpipe.app.browser.file.LocalFileSystem;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.ProcessControlProvider;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.util.FailableRunnable;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -69,7 +69,7 @@ public class BrowserClipboard {
|
|||
|
||||
@SneakyThrows
|
||||
public static ClipboardContent startDrag(
|
||||
FileSystem.FileEntry base, List<BrowserEntry> selected, BrowserFileTransferMode mode) {
|
||||
FileEntry base, List<BrowserEntry> selected, BrowserFileTransferMode mode) {
|
||||
if (selected.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public class BrowserClipboard {
|
|||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void startCopy(FileSystem.FileEntry base, List<BrowserEntry> selected) {
|
||||
public static void startCopy(FileEntry base, List<BrowserEntry> selected) {
|
||||
if (selected.isEmpty()) {
|
||||
currentCopyClipboard.setValue(null);
|
||||
return;
|
||||
|
@ -118,7 +118,7 @@ public class BrowserClipboard {
|
|||
@Value
|
||||
public static class Instance {
|
||||
UUID uuid;
|
||||
FileSystem.FileEntry baseDirectory;
|
||||
FileEntry baseDirectory;
|
||||
List<BrowserEntry> entries;
|
||||
BrowserFileTransferMode mode;
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@ import io.xpipe.app.prefs.AppPrefs;
|
|||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.FileBridge;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BrowserFileOpener {
|
||||
|
||||
public static void openWithAnyApplication(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||
public static void openWithAnyApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||
var file = entry.getPath();
|
||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
||||
FileBridge.get()
|
||||
|
@ -33,7 +33,7 @@ public class BrowserFileOpener {
|
|||
s -> FileOpener.openWithAnyApplication(s));
|
||||
}
|
||||
|
||||
public static void openInDefaultApplication(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||
public static void openInDefaultApplication(OpenFileSystemModel model, FileEntry entry) {
|
||||
var file = entry.getPath();
|
||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
||||
FileBridge.get()
|
||||
|
@ -54,7 +54,7 @@ public class BrowserFileOpener {
|
|||
s -> FileOpener.openInDefaultApplication(s));
|
||||
}
|
||||
|
||||
public static void openInTextEditor(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||
public static void openInTextEditor(OpenFileSystemModel model, FileEntry entry) {
|
||||
var editor = AppPrefs.get().externalEditor().getValue();
|
||||
if (editor == null) {
|
||||
return;
|
||||
|
|
|
@ -10,7 +10,7 @@ import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -40,10 +40,10 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
|
||||
var commonPlatform = FXCollections.<FileSystem.FileEntry>observableArrayList();
|
||||
var commonPlatform = FXCollections.<FileEntry>observableArrayList();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var common = sc.getOsType().determineInterestingPaths(sc).stream()
|
||||
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
|
||||
.map(s -> FileEntry.ofDirectory(model.getFileSystem(), s))
|
||||
.filter(entry -> {
|
||||
try {
|
||||
return sc.getShellDialect()
|
||||
|
@ -63,15 +63,16 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview)
|
||||
.apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));
|
||||
|
||||
var roots = sc.getShellDialect()
|
||||
.listRoots(sc)
|
||||
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
|
||||
var roots = model.getFileSystem()
|
||||
.listRoots()
|
||||
.stream()
|
||||
.map(s -> FileEntry.ofDirectory(model.getFileSystem(), s))
|
||||
.toList();
|
||||
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
|
||||
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
|
||||
|
||||
var recent = new DerivedObservableList<>(model.getSavedState().getRecentDirectories(), true)
|
||||
.mapped(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
|
||||
.mapped(s -> FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()))
|
||||
.getList();
|
||||
var recentOverview = new BrowserFileOverviewComp(model, recent, true);
|
||||
var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview);
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.List;
|
|||
public class BrowserActionFormatter {
|
||||
|
||||
public static String filesArgument(List<BrowserEntry> entries) {
|
||||
return entries.size() == 1 ? entries.getFirst().getOptionallyQuotedFileName() : "(" + entries.size() + ")";
|
||||
return entries.size() == 1 ? entries.getFirst().getFileName() : "(" + entries.size() + ")";
|
||||
}
|
||||
|
||||
public static String centerEllipsis(String input, int length) {
|
||||
|
|
|
@ -2,9 +2,9 @@ package io.xpipe.app.browser.file;
|
|||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
|
@ -45,7 +45,7 @@ public class BrowserAlerts {
|
|||
.orElse(FileConflictChoice.CANCEL);
|
||||
}
|
||||
|
||||
public static boolean showMoveAlert(List<FileSystem.FileEntry> source, FileSystem.FileEntry target) {
|
||||
public static boolean showMoveAlert(List<FileEntry> source, FileEntry target) {
|
||||
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class BrowserAlerts {
|
|||
.orElse(false);
|
||||
}
|
||||
|
||||
public static boolean showDeleteAlert(List<FileSystem.FileEntry> source) {
|
||||
public static boolean showDeleteAlert(List<FileEntry> source) {
|
||||
if (source.stream().noneMatch(entry -> entry.getKind() == FileKind.DIRECTORY)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public class BrowserAlerts {
|
|||
.orElse(false);
|
||||
}
|
||||
|
||||
private static String getSelectedElementsString(List<FileSystem.FileEntry> source) {
|
||||
private static String getSelectedElementsString(List<FileEntry> source) {
|
||||
var namesHeader = AppI18n.get("selectedElements");
|
||||
var names = namesHeader + "\n"
|
||||
+ source.stream()
|
||||
|
|
|
@ -2,28 +2,27 @@ package io.xpipe.app.browser.file;
|
|||
|
||||
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BrowserEntry {
|
||||
|
||||
private final BrowserFileListModel model;
|
||||
private final FileSystem.FileEntry rawFileEntry;
|
||||
private final FileEntry rawFileEntry;
|
||||
private final BrowserIconFileType fileType;
|
||||
private final BrowserIconDirectoryType directoryType;
|
||||
|
||||
public BrowserEntry(FileSystem.FileEntry rawFileEntry, BrowserFileListModel model) {
|
||||
public BrowserEntry(FileEntry rawFileEntry, BrowserFileListModel model) {
|
||||
this.rawFileEntry = rawFileEntry;
|
||||
this.model = model;
|
||||
this.fileType = fileType(rawFileEntry);
|
||||
this.directoryType = directoryType(rawFileEntry);
|
||||
}
|
||||
|
||||
private static BrowserIconFileType fileType(FileSystem.FileEntry rawFileEntry) {
|
||||
private static BrowserIconFileType fileType(FileEntry rawFileEntry) {
|
||||
if (rawFileEntry == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -42,7 +41,7 @@ public class BrowserEntry {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static BrowserIconDirectoryType directoryType(FileSystem.FileEntry rawFileEntry) {
|
||||
private static BrowserIconDirectoryType directoryType(FileEntry rawFileEntry) {
|
||||
if (rawFileEntry == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -74,11 +73,6 @@ public class BrowserEntry {
|
|||
}
|
||||
|
||||
public String getFileName() {
|
||||
return getRawFileEntry().getName();
|
||||
}
|
||||
|
||||
public String getOptionallyQuotedFileName() {
|
||||
var n = getFileName();
|
||||
return FileNames.quoteIfNecessary(n);
|
||||
return FileNames.getFileName(getRawFileEntry().getPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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.comp.base.LazyTextFieldComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
@ -8,10 +10,10 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileInfo;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
|
@ -32,9 +34,6 @@ import javafx.scene.layout.HBox;
|
|||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
|
@ -58,6 +57,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
|
||||
private final BrowserFileListModel fileList;
|
||||
private final StringProperty typedSelection = new SimpleStringProperty("");
|
||||
private final DoubleProperty ownerWidth = new SimpleDoubleProperty();
|
||||
|
||||
public BrowserFileListComp(BrowserFileListModel fileList) {
|
||||
this.fileList = fileList;
|
||||
|
@ -103,13 +103,24 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
var modeCol = new TableColumn<BrowserEntry, String>();
|
||||
modeCol.textProperty().bind(AppI18n.observable("attributes"));
|
||||
modeCol.setCellValueFactory(param -> new SimpleObjectProperty<>(
|
||||
param.getValue().getRawFileEntry().resolved().getMode()));
|
||||
param.getValue().getRawFileEntry().resolved().getInfo() instanceof FileInfo.Unix u ? u.getPermissions() : null));
|
||||
modeCol.setCellFactory(col -> new FileModeCell());
|
||||
modeCol.setResizable(false);
|
||||
modeCol.setPrefWidth(120);
|
||||
modeCol.setSortable(false);
|
||||
modeCol.setReorderable(false);
|
||||
|
||||
var ownerCol = new TableColumn<BrowserEntry, String>();
|
||||
ownerCol.textProperty().bind(AppI18n.observable("owner"));
|
||||
ownerCol.setCellValueFactory(param -> {
|
||||
return new SimpleObjectProperty<>(formatOwner(param.getValue()));
|
||||
});
|
||||
ownerCol.setCellFactory(col -> new FileOwnerCell());
|
||||
ownerCol.setSortable(false);
|
||||
ownerCol.setReorderable(false);
|
||||
ownerCol.prefWidthProperty().bind(ownerWidth);
|
||||
ownerCol.setResizable(false);
|
||||
|
||||
var table = new TableView<BrowserEntry>();
|
||||
table.setAccessibleText("Directory contents");
|
||||
table.setPlaceholder(new Region());
|
||||
|
@ -121,18 +132,39 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
fileList.setComparator(table.getComparator());
|
||||
return true;
|
||||
});
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_NEXT_COLUMN);
|
||||
table.setFixedCellSize(32.0);
|
||||
|
||||
table.widthProperty().subscribe((newValue) -> {
|
||||
ownerCol.setVisible(newValue.doubleValue() > 1000);
|
||||
});
|
||||
|
||||
prepareTableSelectionModel(table);
|
||||
prepareTableShortcuts(table);
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, mtimeCol, modeCol);
|
||||
prepareTableChanges(table, mtimeCol, modeCol, ownerCol);
|
||||
prepareTypedSelectionModel(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private String formatOwner(BrowserEntry param) {
|
||||
FileInfo.Unix unix = param.getRawFileEntry().resolved().getInfo() instanceof FileInfo.Unix u ? u : null;
|
||||
if (unix == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var m = fileList.getFileSystemModel();
|
||||
var user = unix.getUser() != null ? unix.getUser() : m.getCache().getUsers().get(unix.getUid());
|
||||
var group = unix.getGroup() != null ? unix.getGroup() : m.getCache().getGroups().get(unix.getGid());
|
||||
var uid = String.valueOf(unix.getUid() != null ? unix.getUid() : m.getCache().getUidForUser(user)).replaceAll("000$", "k");
|
||||
var gid = String.valueOf(unix.getGid() != null ? unix.getGid() : m.getCache().getGidForGroup(group)).replaceAll("000$", "k");
|
||||
if (uid.equals(gid)) {
|
||||
return user + " [" + uid + "]";
|
||||
}
|
||||
return user + " [" + uid + "] / " + group + " [" + gid + "]";
|
||||
}
|
||||
|
||||
private void prepareTypedSelectionModel(TableView<BrowserEntry> table) {
|
||||
AtomicReference<Instant> lastFail = new AtomicReference<>();
|
||||
table.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
|
@ -369,8 +401,9 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
private void prepareTableChanges(
|
||||
TableView<BrowserEntry> table,
|
||||
TableColumn<BrowserEntry, Instant> mtimeCol,
|
||||
TableColumn<BrowserEntry, String> modeCol) {
|
||||
var lastDir = new SimpleObjectProperty<FileSystem.FileEntry>();
|
||||
TableColumn<BrowserEntry, String> modeCol,
|
||||
TableColumn<BrowserEntry, String> ownerCol) {
|
||||
var lastDir = new SimpleObjectProperty<FileEntry>();
|
||||
Runnable updateHandler = () -> {
|
||||
Platform.runLater(() -> {
|
||||
var newItems = new ArrayList<>(fileList.getShown().getValue());
|
||||
|
@ -386,18 +419,28 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
}
|
||||
|
||||
ownerWidth.set(fileList.getAll().getValue().stream()
|
||||
.map(browserEntry -> formatOwner(browserEntry))
|
||||
.map(s -> s != null ? s.length() * 10 : 0)
|
||||
.max(Comparator.naturalOrder()).orElse(150));
|
||||
if (fileList.getFileSystemModel().getFileSystem() != null) {
|
||||
var shell = fileList.getFileSystemModel()
|
||||
.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow();
|
||||
var hasAttributes = !OsType.WINDOWS.equals(shell.getOsType());
|
||||
if (!hasAttributes) {
|
||||
var notWindows = !OsType.WINDOWS.equals(shell.getOsType());
|
||||
if (!notWindows) {
|
||||
table.getColumns().remove(modeCol);
|
||||
table.getColumns().remove(ownerCol);
|
||||
} else {
|
||||
if (!table.getColumns().contains(modeCol)) {
|
||||
table.getColumns().add(modeCol);
|
||||
}
|
||||
if (!table.getColumns().contains(ownerCol)) {
|
||||
table.getColumns().add(table.getColumns().size() - 1, ownerCol);
|
||||
} else {
|
||||
table.getColumns().remove(ownerCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -495,6 +538,23 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
}
|
||||
}
|
||||
|
||||
private static class FileOwnerCell extends TableCell<BrowserEntry, String> {
|
||||
|
||||
public FileOwnerCell() {
|
||||
setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(String owner, boolean empty) {
|
||||
super.updateItem(owner, empty);
|
||||
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileTimeCell extends TableCell<BrowserEntry, Instant> {
|
||||
|
||||
@Override
|
||||
|
@ -648,7 +708,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
.getPath()
|
||||
: getTableRow().getItem().getFileName();
|
||||
var fileName = normalName;
|
||||
var hidden = getTableRow().getItem().getRawFileEntry().isHidden() || fileName.startsWith(".");
|
||||
var hidden = getTableRow().getItem().getRawFileEntry().getInfo().explicitlyHidden() || fileName.startsWith(".");
|
||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||
text.set(fileName);
|
||||
// Visibility seems to be bugged, so use opacity
|
||||
|
|
|
@ -3,9 +3,9 @@ package io.xpipe.app.browser.file;
|
|||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -55,7 +55,7 @@ public final class BrowserFileListModel {
|
|||
});
|
||||
}
|
||||
|
||||
public void setAll(Stream<FileSystem.FileEntry> newFiles) {
|
||||
public void setAll(Stream<FileEntry> newFiles) {
|
||||
try (var s = newFiles) {
|
||||
var l = s.filter(entry -> entry != null)
|
||||
.map(entry -> new BrowserEntry(entry, this))
|
||||
|
|
|
@ -8,7 +8,7 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -26,12 +26,12 @@ import java.util.function.Function;
|
|||
public class BrowserFileOverviewComp extends SimpleComp {
|
||||
|
||||
OpenFileSystemModel model;
|
||||
ObservableList<FileSystem.FileEntry> list;
|
||||
ObservableList<FileEntry> list;
|
||||
boolean grow;
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
Function<FileSystem.FileEntry, Comp<?>> factory = entry -> {
|
||||
Function<FileEntry, Comp<?>> factory = entry -> {
|
||||
return Comp.of(() -> {
|
||||
var icon = BrowserIcons.createIcon(entry);
|
||||
var graphic = new HorizontalComp(List.of(
|
||||
|
|
|
@ -2,10 +2,7 @@ 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 io.xpipe.core.store.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
|
@ -18,8 +15,8 @@ import java.util.function.Consumer;
|
|||
|
||||
public class BrowserFileTransferOperation {
|
||||
|
||||
private final FileSystem.FileEntry target;
|
||||
private final List<FileSystem.FileEntry> files;
|
||||
private final FileEntry target;
|
||||
private final List<FileEntry> files;
|
||||
private final BrowserFileTransferMode transferMode;
|
||||
private final boolean checkConflicts;
|
||||
private final Consumer<BrowserTransferProgress> progress;
|
||||
|
@ -27,8 +24,8 @@ public class BrowserFileTransferOperation {
|
|||
BrowserAlerts.FileConflictChoice lastConflictChoice;
|
||||
|
||||
public BrowserFileTransferOperation(
|
||||
FileSystem.FileEntry target,
|
||||
List<FileSystem.FileEntry> files,
|
||||
FileEntry target,
|
||||
List<FileEntry> files,
|
||||
BrowserFileTransferMode transferMode,
|
||||
boolean checkConflicts,
|
||||
Consumer<BrowserTransferProgress> progress) {
|
||||
|
@ -40,7 +37,7 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
|
||||
public static BrowserFileTransferOperation ofLocal(
|
||||
FileSystem.FileEntry target,
|
||||
FileEntry target,
|
||||
List<Path> files,
|
||||
BrowserFileTransferMode transferMode,
|
||||
boolean checkConflicts,
|
||||
|
@ -133,7 +130,7 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleSingleOnSameFileSystem(FileSystem.FileEntry source) throws Exception {
|
||||
private void handleSingleOnSameFileSystem(FileEntry source) throws Exception {
|
||||
// Prevent dropping directory into itself
|
||||
if (source.getPath().equals(target.getPath())) {
|
||||
return;
|
||||
|
@ -163,12 +160,12 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleSingleAcrossFileSystems(FileSystem.FileEntry source) throws Exception {
|
||||
private void handleSingleAcrossFileSystems(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>();
|
||||
var flatFiles = new LinkedHashMap<FileEntry, String>();
|
||||
|
||||
// Prevent dropping directory into itself
|
||||
if (source.getFileSystem().equals(target.getFileSystem())
|
||||
|
@ -182,8 +179,8 @@ public class BrowserFileTransferOperation {
|
|||
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) {
|
||||
List<FileEntry> list = source.getFileSystem().listFilesRecursively(source.getPath());
|
||||
for (FileEntry fileEntry : list) {
|
||||
var rel = FileNames.toUnix(FileNames.relativize(baseRelative, fileEntry.getPath()));
|
||||
flatFiles.put(fileEntry, rel);
|
||||
if (fileEntry.getKind() == FileKind.FILE) {
|
||||
|
@ -225,7 +222,7 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
|
||||
private void transfer(
|
||||
FileSystem.FileEntry sourceFile,
|
||||
FileEntry sourceFile,
|
||||
String targetFile,
|
||||
AtomicLong transferred,
|
||||
AtomicLong totalSize,
|
||||
|
@ -297,14 +294,14 @@ public class BrowserFileTransferOperation {
|
|||
}
|
||||
}
|
||||
|
||||
private void deleteSingle(FileSystem.FileEntry source) throws Exception {
|
||||
private void deleteSingle(FileEntry source) throws Exception {
|
||||
source.getFileSystem().delete(source.getPath());
|
||||
}
|
||||
|
||||
private static final int DEFAULT_BUFFER_SIZE = 1024;
|
||||
|
||||
private void transferFile(
|
||||
FileSystem.FileEntry sourceFile,
|
||||
FileEntry sourceFile,
|
||||
InputStream inputStream,
|
||||
OutputStream outputStream,
|
||||
AtomicLong transferred,
|
||||
|
|
|
@ -6,8 +6,8 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
|||
import io.xpipe.app.util.BooleanAnimationTimer;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -92,7 +92,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
|||
}
|
||||
|
||||
private List<MenuItem> updateMenuItems(Menu m, BrowserEntry entry, boolean updateInstantly) throws Exception {
|
||||
List<FileSystem.FileEntry> list = new ArrayList<>();
|
||||
List<FileEntry> list = new ArrayList<>();
|
||||
model.withFiles(entry.getRawFileEntry().resolved().getPath(), newFiles -> {
|
||||
try (var s = newFiles) {
|
||||
var l = s.map(fileEntry -> fileEntry.resolved()).toList();
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.browser.file;
|
|||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
@ -125,19 +126,17 @@ public class FileSystemHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static FileSystem.FileEntry getRemoteWrapper(FileSystem fileSystem, String file) throws Exception {
|
||||
return new FileSystem.FileEntry(
|
||||
public static FileEntry getRemoteWrapper(FileSystem fileSystem, String file) throws Exception {
|
||||
return new FileEntry(
|
||||
fileSystem,
|
||||
file,
|
||||
Instant.now(),
|
||||
false,
|
||||
false,
|
||||
fileSystem.getFileSize(file),
|
||||
null,
|
||||
fileSystem.directoryExists(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
||||
}
|
||||
|
||||
public static void delete(List<FileSystem.FileEntry> files) {
|
||||
public static void delete(List<FileEntry> files) {
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
|
@ -18,17 +19,15 @@ public class LocalFileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
public static FileSystem.FileEntry getLocalFileEntry(Path file) throws Exception {
|
||||
public static FileEntry getLocalFileEntry(Path file) throws Exception {
|
||||
if (localFileSystem == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return new FileSystem.FileEntry(
|
||||
return new FileEntry(
|
||||
localFileSystem.open(),
|
||||
file.toString(),
|
||||
Files.getLastModifiedTime(file).toInstant(),
|
||||
Files.isHidden(file),
|
||||
Files.isExecutable(file),
|
||||
Files.size(file),
|
||||
null,
|
||||
Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE);
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
package io.xpipe.app.browser.fs;
|
||||
|
||||
import io.xpipe.app.util.ShellControlCache;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialect;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class OpenFileSystemCache extends ShellControlCache {
|
||||
|
||||
private final OpenFileSystemModel model;
|
||||
private final String username;
|
||||
private final Map<Integer, String> users = new HashMap<>();
|
||||
private final Map<Integer, String> groups = new HashMap<>();
|
||||
|
||||
public OpenFileSystemCache(OpenFileSystemModel model) throws Exception {
|
||||
super(model.getFileSystem().getShell().orElseThrow());
|
||||
|
@ -20,6 +27,42 @@ public class OpenFileSystemCache extends ShellControlCache {
|
|||
ShellDialect d = sc.getShellDialect();
|
||||
// If there is no id command, we should still be fine with just assuming root
|
||||
username = d.printUsernameCommand(sc).readStdoutIfPossible().orElse("root");
|
||||
loadUsers();
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
public int getUidForUser(String name) {
|
||||
return users.entrySet().stream().filter(e -> e.getValue().equals(name)).findFirst().map(e -> e.getKey()).orElse(0);
|
||||
}
|
||||
|
||||
public int getGidForGroup(String name) {
|
||||
return groups.entrySet().stream().filter(e -> e.getValue().equals(name)).findFirst().map(e -> e.getKey()).orElse(0);
|
||||
}
|
||||
|
||||
private void loadUsers() throws Exception {
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
if (sc.getOsType() == OsType.WINDOWS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = sc.command(CommandBuilder.of().add("cat").addFile("/etc/passwd")).readStdoutOrThrow();
|
||||
lines.lines().forEach(s -> {
|
||||
var split = s.split(":");
|
||||
users.put(Integer.parseInt(split[2]), split[0]);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadGroups() throws Exception {
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
if (sc.getOsType() == OsType.WINDOWS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = sc.command(CommandBuilder.of().add("cat").addFile("/etc/group")).readStdoutOrThrow();
|
||||
lines.lines().forEach(s -> {
|
||||
var split = s.split(":");
|
||||
groups.put(Integer.parseInt(split[2]), split[0]);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
|
|
|
@ -166,7 +166,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
cdSyncWithoutCheck(currentPath.get());
|
||||
}
|
||||
|
||||
public FileSystem.FileEntry getCurrentParentDirectory() {
|
||||
public FileEntry getCurrentParentDirectory() {
|
||||
var current = getCurrentDirectory();
|
||||
if (current == null) {
|
||||
return null;
|
||||
|
@ -177,10 +177,10 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
return null;
|
||||
}
|
||||
|
||||
return new FileSystem.FileEntry(fileSystem, parent, null, false, false, 0, null, FileKind.DIRECTORY);
|
||||
return new FileEntry(fileSystem, parent, null, 0, null, FileKind.DIRECTORY);
|
||||
}
|
||||
|
||||
public FileSystem.FileEntry getCurrentDirectory() {
|
||||
public FileEntry getCurrentDirectory() {
|
||||
if (currentPath.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
return null;
|
||||
}
|
||||
|
||||
return new FileSystem.FileEntry(fileSystem, currentPath.get(), null, false, false, 0, null, FileKind.DIRECTORY);
|
||||
return new FileEntry(fileSystem, currentPath.get(), null, 0, null, FileKind.DIRECTORY);
|
||||
}
|
||||
|
||||
public void cdAsync(String path) {
|
||||
|
@ -305,7 +305,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
loadFilesSync(path);
|
||||
}
|
||||
|
||||
public void withFiles(String dir, FailableConsumer<Stream<FileSystem.FileEntry>, Exception> consumer)
|
||||
public void withFiles(String dir, FailableConsumer<Stream<FileEntry>, Exception> consumer)
|
||||
throws Exception {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
if (dir != null) {
|
||||
|
@ -341,7 +341,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
}
|
||||
}
|
||||
|
||||
public void dropLocalFilesIntoAsync(FileSystem.FileEntry entry, List<Path> files) {
|
||||
public void dropLocalFilesIntoAsync(FileEntry entry, List<Path> files) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
if (fileSystem == null) {
|
||||
|
@ -358,7 +358,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
}
|
||||
|
||||
public void dropFilesIntoAsync(
|
||||
FileSystem.FileEntry target, List<FileSystem.FileEntry> files, BrowserFileTransferMode mode) {
|
||||
FileEntry target, List<FileEntry> files, BrowserFileTransferMode mode) {
|
||||
// We don't have to do anything in this case
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -36,12 +37,12 @@ public abstract class BrowserIconDirectoryType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FileSystem.FileEntry entry) {
|
||||
public boolean matches(FileEntry entry) {
|
||||
return entry.getPath().equals("/") || entry.getPath().matches("\\w:\\\\");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
public String getIcon(FileEntry entry, boolean open) {
|
||||
return open ? "default_root_folder_opened.svg" : "default_root_folder.svg";
|
||||
}
|
||||
});
|
||||
|
@ -81,9 +82,9 @@ public abstract class BrowserIconDirectoryType {
|
|||
|
||||
public abstract String getId();
|
||||
|
||||
public abstract boolean matches(FileSystem.FileEntry entry);
|
||||
public abstract boolean matches(FileEntry entry);
|
||||
|
||||
public abstract String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||
public abstract String getIcon(FileEntry entry, boolean open);
|
||||
|
||||
public static class Simple extends BrowserIconDirectoryType {
|
||||
|
||||
|
@ -102,16 +103,17 @@ public abstract class BrowserIconDirectoryType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FileSystem.FileEntry entry) {
|
||||
public boolean matches(FileEntry entry) {
|
||||
if (entry.getKind() != FileKind.DIRECTORY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return names.contains(entry.getName());
|
||||
var name = FileNames.getFileName(entry.getPath());
|
||||
return names.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
public String getIcon(FileEntry entry, boolean open) {
|
||||
return open ? this.open.getIcon() : this.closed.getIcon();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -60,7 +61,7 @@ public abstract class BrowserIconFileType {
|
|||
|
||||
public abstract String getId();
|
||||
|
||||
public abstract boolean matches(FileSystem.FileEntry entry);
|
||||
public abstract boolean matches(FileEntry entry);
|
||||
|
||||
public abstract String getIcon();
|
||||
|
||||
|
@ -78,14 +79,16 @@ public abstract class BrowserIconFileType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FileSystem.FileEntry entry) {
|
||||
public boolean matches(FileEntry entry) {
|
||||
if (entry.getKind() == FileKind.DIRECTORY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (entry.getExtension() != null
|
||||
&& endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT)))
|
||||
|| endings.contains(entry.getName());
|
||||
var name = FileNames.getFileName(entry.getPath());
|
||||
var ext = FileNames.getExtension(entry.getPath());
|
||||
return (ext != null
|
||||
&& endings.contains("." + ext.toLowerCase(Locale.ROOT)))
|
||||
|| endings.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser.icon;
|
|||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
public class BrowserIcons {
|
||||
|
||||
|
@ -18,7 +18,7 @@ public class BrowserIcons {
|
|||
return PrettyImageHelper.ofFixedSizeSquare(type.getIcon(), 24);
|
||||
}
|
||||
|
||||
public static Comp<?> createIcon(FileSystem.FileEntry entry) {
|
||||
public static Comp<?> createIcon(FileEntry entry) {
|
||||
return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.xpipe.app.browser.icon;
|
|||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
public class FileIconManager {
|
||||
|
||||
|
@ -18,7 +18,7 @@ public class FileIconManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static synchronized String getFileIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
public static synchronized String getFileIcon(FileEntry entry, boolean open) {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import io.xpipe.core.util.NewLine;
|
||||
|
@ -79,7 +80,7 @@ public interface ShellDialect {
|
|||
|
||||
String assembleCommand(String command, Map<String, String> variables);
|
||||
|
||||
Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception;
|
||||
Stream<FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception;
|
||||
|
||||
Stream<String> listRoots(ShellControl control) throws Exception;
|
||||
|
||||
|
|
49
core/src/main/java/io/xpipe/core/store/FileEntry.java
Normal file
49
core/src/main/java/io/xpipe/core/store/FileEntry.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.NonFinal;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Value
|
||||
@NonFinal
|
||||
public class FileEntry {
|
||||
FileSystem fileSystem;
|
||||
Instant date;
|
||||
long size;
|
||||
|
||||
FileInfo info;
|
||||
|
||||
@NonNull
|
||||
FileKind kind;
|
||||
|
||||
@NonNull
|
||||
@NonFinal
|
||||
@Setter
|
||||
String path;
|
||||
|
||||
public FileEntry(
|
||||
FileSystem fileSystem, @NonNull String path, Instant date, long size, FileInfo info, @NonNull FileKind kind
|
||||
) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.kind = kind;
|
||||
this.path = kind == FileKind.DIRECTORY ? new FilePath(path).toDirectory().toString() : path;
|
||||
this.date = date;
|
||||
this.info = info;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public static FileEntry ofDirectory(FileSystem fileSystem, String path) {
|
||||
return new FileEntry(fileSystem, path, Instant.now(), 0, null, FileKind.DIRECTORY);
|
||||
}
|
||||
|
||||
public FileEntry resolved() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return FileNames.getFileName(path);
|
||||
}
|
||||
}
|
46
core/src/main/java/io/xpipe/core/store/FileInfo.java
Normal file
46
core/src/main/java/io/xpipe/core/store/FileInfo.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
public sealed interface FileInfo permits FileInfo.Windows, FileInfo.Unix {
|
||||
|
||||
boolean explicitlyHidden();
|
||||
|
||||
boolean possiblyExecutable();
|
||||
|
||||
@Value
|
||||
class Windows implements FileInfo {
|
||||
|
||||
String attributes;
|
||||
|
||||
@Override
|
||||
public boolean explicitlyHidden() {
|
||||
return attributes.contains("h");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean possiblyExecutable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
class Unix implements FileInfo {
|
||||
|
||||
String permissions;
|
||||
Integer uid;
|
||||
String user;
|
||||
Integer gid;
|
||||
String group;
|
||||
|
||||
@Override
|
||||
public boolean explicitlyHidden() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean possiblyExecutable() {
|
||||
return permissions.contains("x");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,15 +2,9 @@ package io.xpipe.core.store;
|
|||
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.NonFinal;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
@ -62,7 +56,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
try {
|
||||
var list = new ArrayList<FileEntry>();
|
||||
list.add(fileEntry);
|
||||
list.addAll(listFilesRecursively(fileEntry.getPath()));
|
||||
list.addAll(listFilesRecursively(fileEntry.getPath().toString()));
|
||||
return list.stream();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -73,87 +67,4 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
|
||||
List<String> listRoots() throws Exception;
|
||||
|
||||
@Value
|
||||
@NonFinal
|
||||
class FileEntry {
|
||||
FileSystem fileSystem;
|
||||
Instant date;
|
||||
boolean hidden;
|
||||
Boolean executable;
|
||||
long size;
|
||||
String mode;
|
||||
|
||||
@NonNull
|
||||
FileKind kind;
|
||||
|
||||
@NonNull
|
||||
@NonFinal
|
||||
String path;
|
||||
|
||||
@NonFinal
|
||||
String extension;
|
||||
|
||||
@NonFinal
|
||||
String name;
|
||||
|
||||
public FileEntry(
|
||||
FileSystem fileSystem,
|
||||
@NonNull String path,
|
||||
Instant date,
|
||||
boolean hidden,
|
||||
Boolean executable,
|
||||
long size,
|
||||
String mode,
|
||||
@NonNull FileKind kind) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.mode = mode;
|
||||
this.kind = kind;
|
||||
this.path = kind == FileKind.DIRECTORY ? FileNames.toDirectory(path) : path;
|
||||
this.extension = FileNames.getExtension(path);
|
||||
this.name = FileNames.getFileName(path);
|
||||
this.date = date;
|
||||
this.hidden = hidden;
|
||||
this.executable = executable;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public static FileEntry ofDirectory(FileSystem fileSystem, String path) {
|
||||
return new FileEntry(fileSystem, path, Instant.now(), true, false, 0, null, FileKind.DIRECTORY);
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
this.extension = FileNames.getExtension(path);
|
||||
this.name = FileNames.getFileName(path);
|
||||
}
|
||||
|
||||
public FileEntry resolved() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
class LinkFileEntry extends FileEntry {
|
||||
|
||||
@NonNull
|
||||
FileEntry target;
|
||||
|
||||
public LinkFileEntry(
|
||||
@NonNull FileSystem fileSystem,
|
||||
@NonNull String path,
|
||||
Instant date,
|
||||
boolean hidden,
|
||||
Boolean executable,
|
||||
long size,
|
||||
String mode,
|
||||
@NonNull FileEntry target) {
|
||||
super(fileSystem, path, date, hidden, executable, size, mode, FileKind.LINK);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public FileEntry resolved() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
27
core/src/main/java/io/xpipe/core/store/LinkFileEntry.java
Normal file
27
core/src/main/java/io/xpipe/core/store/LinkFileEntry.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package io.xpipe.core.store;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LinkFileEntry extends FileEntry {
|
||||
|
||||
@NonNull
|
||||
FileEntry target;
|
||||
|
||||
public LinkFileEntry(
|
||||
FileSystem fileSystem, @NonNull String path, Instant date, long size, @NonNull FileInfo info,
|
||||
@NonNull FileEntry target
|
||||
) {
|
||||
super(fileSystem, path, date, size, info, FileKind.LINK);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public FileEntry resolved() {
|
||||
return target;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.browser.file.BrowserEntry;
|
|||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
|
@ -34,6 +35,6 @@ public class JavapAction extends ToFileCommandAction implements FileTypeAction,
|
|||
|
||||
@Override
|
||||
protected String createCommand(OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return "javap -c -p " + entry.getOptionallyQuotedFileName();
|
||||
return "javap -c -p " + FileNames.quoteIfNecessary(entry.getRawFileEntry().getPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import io.xpipe.core.process.CommandBuilder;
|
|||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.Node;
|
||||
|
@ -20,12 +20,12 @@ import java.util.stream.Stream;
|
|||
|
||||
public class RunAction extends MultiExecuteAction {
|
||||
|
||||
private boolean isExecutable(FileSystem.FileEntry e) {
|
||||
private boolean isExecutable(FileEntry e) {
|
||||
if (e.getKind() != FileKind.FILE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.getExecutable() != null && e.getExecutable()) {
|
||||
if (e.getInfo() != null && e.getInfo().possiblyExecutable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public class UnzipAction extends ExecuteApplicationAction implements FileTypeAct
|
|||
|
||||
@Override
|
||||
protected String createCommand(OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return "unzip -o " + entry.getOptionallyQuotedFileName() + " -d "
|
||||
return "unzip -o " + FileNames.quoteIfNecessary(entry.getRawFileEntry().getPath()) + " -d "
|
||||
+ FileNames.quoteIfNecessary(FileNames.getBaseName(entry.getFileName()));
|
||||
}
|
||||
|
||||
|
|
|
@ -440,6 +440,7 @@ sshConfiguration=SSH-konfiguration
|
|||
size=Størrelse
|
||||
attributes=Attributter
|
||||
modified=Ændret
|
||||
owner=Ejer
|
||||
isOnlySupported=understøttes kun med en licens
|
||||
areOnlySupported=understøttes kun med en licens
|
||||
updateReadyTitle=Opdatering til $VERSION$ klar
|
||||
|
|
|
@ -436,6 +436,7 @@ sshConfiguration=SSH-Konfiguration
|
|||
size=Größe
|
||||
attributes=Attribute
|
||||
modified=Geändert
|
||||
owner=Eigentümer
|
||||
isOnlySupported=wird nur mit einer Lizenz unterstützt
|
||||
areOnlySupported=werden nur mit einer Lizenz unterstützt
|
||||
updateReadyTitle=Update auf $VERSION$ bereit
|
||||
|
|
|
@ -439,6 +439,7 @@ size=Size
|
|||
attributes=Attributes
|
||||
#context: title, last modified date
|
||||
modified=Modified
|
||||
owner=Owner
|
||||
isOnlySupported=is only supported with a license
|
||||
areOnlySupported=are only supported with a license
|
||||
updateReadyTitle=Update to $VERSION$ ready
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=Configuración SSH
|
|||
size=Tamaño
|
||||
attributes=Atributos
|
||||
modified=Modificado
|
||||
owner=Propietario
|
||||
isOnlySupported=sólo es compatible con una licencia
|
||||
areOnlySupported=sólo se admiten con licencia
|
||||
updateReadyTitle=Actualiza a $VERSION$ ready
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=Configuration SSH
|
|||
size=Taille
|
||||
attributes=Attributs
|
||||
modified=Modifié
|
||||
owner=Propriétaire
|
||||
isOnlySupported=n'est pris en charge qu'avec une licence
|
||||
areOnlySupported=ne sont prises en charge qu'avec une licence
|
||||
updateReadyTitle=Mise à jour de $VERSION$ ready
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=Configurazione SSH
|
|||
size=Dimensione
|
||||
attributes=Attributi
|
||||
modified=Modificato
|
||||
owner=Proprietario
|
||||
isOnlySupported=è supportato solo con una licenza
|
||||
areOnlySupported=sono supportati solo con una licenza
|
||||
updateReadyTitle=Aggiornamento a $VERSION$ ready
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=SSHの設定
|
|||
size=サイズ
|
||||
attributes=属性
|
||||
modified=変更された
|
||||
owner=所有者
|
||||
isOnlySupported=がサポートされているのは、ライセンス
|
||||
areOnlySupported=がサポートされるのはライセンスが必要である
|
||||
updateReadyTitle=$VERSION$ に更新
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=SSH-configuratie
|
|||
size=Grootte
|
||||
attributes=Attributen
|
||||
modified=Gewijzigd
|
||||
owner=Eigenaar
|
||||
isOnlySupported=wordt alleen ondersteund met een licentie
|
||||
areOnlySupported=worden alleen ondersteund met een licentie
|
||||
updateReadyTitle=Bijwerken naar $VERSION$ klaar
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=Configuração SSH
|
|||
size=Tamanho
|
||||
attributes=Atribui
|
||||
modified=Modificado
|
||||
owner=Proprietário
|
||||
isOnlySupported=só é suportado com uma licença
|
||||
areOnlySupported=só são suportados com uma licença
|
||||
updateReadyTitle=Actualiza para $VERSION$ ready
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=Конфигурация SSH
|
|||
size=Размер
|
||||
attributes=Атрибуты
|
||||
modified=Изменено
|
||||
owner=Владелец
|
||||
isOnlySupported=поддерживается только при наличии лицензии
|
||||
areOnlySupported=поддерживаются только при наличии лицензии
|
||||
updateReadyTitle=Обновление на $VERSION$ готово
|
||||
|
|
|
@ -425,6 +425,7 @@ sshConfiguration=SSH Yapılandırması
|
|||
size=Boyut
|
||||
attributes=Nitelikler
|
||||
modified=Değiştirilmiş
|
||||
owner=Sahibi
|
||||
isOnlySupported=yalnızca bir lisans ile desteklenir
|
||||
areOnlySupported=yalnızca bir lisans ile desteklenir
|
||||
updateReadyTitle=$VERSION$ için güncelleme hazır
|
||||
|
|
|
@ -424,6 +424,7 @@ sshConfiguration=SSH 配置
|
|||
size=大小
|
||||
attributes=属性
|
||||
modified=已修改
|
||||
owner=所有者
|
||||
isOnlySupported=只有获得许可后才支持
|
||||
areOnlySupported=只有获得许可后才支持
|
||||
updateReadyTitle=更新至$VERSION$ ready
|
||||
|
|
Loading…
Reference in a new issue