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