Rework file references for ssh keys

This commit is contained in:
crschnick 2023-12-08 09:11:42 +00:00
parent 7574ff8666
commit e1f62830b1
11 changed files with 101 additions and 156 deletions

View file

@ -5,7 +5,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileStore;
import io.xpipe.app.util.FileReference;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
@ -30,7 +30,7 @@ public class BrowserModel {
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
@Setter
private Consumer<List<FileStore>> onFinish;
private Consumer<List<FileReference>> onFinish;
public BrowserModel(Mode mode) {
this.mode = mode;
@ -100,7 +100,7 @@ public class BrowserModel {
}
var stores = chosen.stream().map(
entry -> new FileStore(entry.getRawFileEntry().getFileSystem().getStore(), entry.getRawFileEntry().getPath())).toList();
entry -> new FileReference(selected.getValue().getEntry(), entry.getRawFileEntry().getPath())).toList();
onFinish.accept(stores);
}

View file

@ -5,7 +5,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileStore;
import io.xpipe.app.util.FileReference;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
import javafx.stage.FileChooser;
@ -20,7 +20,7 @@ import java.util.function.Supplier;
public class StandaloneFileBrowser {
public static void localOpenFileChooser(
Property<FileStore> fileStoreProperty, Window owner, Map<String, List<String>> extensions) {
Property<FileReference> fileStoreProperty, Window owner, Map<String, List<String>> extensions) {
PlatformThread.runLaterIfNeeded(() -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(AppI18n.get("browseFileTitle"));
@ -34,12 +34,12 @@ public class StandaloneFileBrowser {
File file = fileChooser.showOpenDialog(owner);
if (file != null && file.exists()) {
fileStoreProperty.setValue(FileStore.local(file.toPath()));
fileStoreProperty.setValue(FileReference.local(file.toPath()));
}
});
}
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileStore> file) {
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER);
var comp = new BrowserComp(model)
@ -55,7 +55,7 @@ public class StandaloneFileBrowser {
});
}
public static void saveSingleFile(Property<FileStore> file) {
public static void saveSingleFile(Property<FileReference> file) {
PlatformThread.runLaterIfNeeded(() -> {
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_SAVE);
var comp = new BrowserComp(model)

View file

@ -4,11 +4,13 @@ import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@ -16,13 +18,22 @@ import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class FileStoreChoiceComp extends SimpleComp {
public class FileReferenceChoiceComp extends SimpleComp {
private final boolean hideFileSystem;
private final Property<FileSystemStore> fileSystem;
private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem;
private final Property<String> filePath;
public FileStoreChoiceComp(boolean hideFileSystem, Property<FileSystemStore> fileSystem, Property<String> filePath) {
public <T extends FileSystemStore> FileReferenceChoiceComp(ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
this.hideFileSystem = true;
this.fileSystem = new SimpleObjectProperty<>();
SimpleChangeListener.apply(fileSystem, val -> {
this.fileSystem.setValue(val);
});
this.filePath = filePath;
}
public FileReferenceChoiceComp(boolean hideFileSystem, Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem, Property<String> filePath) {
this.hideFileSystem = hideFileSystem;
this.fileSystem = fileSystem != null ? fileSystem : new SimpleObjectProperty<>();
this.filePath = filePath;
@ -42,7 +53,7 @@ public class FileStoreChoiceComp extends SimpleComp {
.grow(false, true);
var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> {
StandaloneFileBrowser.openSingleFile(() -> hideFileSystem ? DataStorage.get().local().ref() : null, fileStore -> {
StandaloneFileBrowser.openSingleFile(() -> hideFileSystem ? fileSystem.getValue() : null, fileStore -> {
if (fileStore == null) {
filePath.setValue(null);
fileSystem.setValue(null);

View file

@ -1,8 +1,9 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
@ -13,40 +14,36 @@ import javafx.scene.layout.Region;
public class FileSystemStoreChoiceComp extends SimpleComp {
private final Property<FileSystemStore> selected;
private final Property<DataStoreEntryRef<? extends FileSystemStore>> selected;
public FileSystemStoreChoiceComp(Property<FileSystemStore> selected) {
public FileSystemStoreChoiceComp(Property<DataStoreEntryRef<? extends FileSystemStore>> selected) {
this.selected = selected;
}
private static String getName(FileSystemStore store) {
return DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(store))
.findAny()
.map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
.orElse("?");
private static String getName(DataStoreEntryRef<? extends FileSystemStore> store) {
return store.get().getName();
}
private Region createGraphic(FileSystemStore s) {
var provider = DataStoreProviders.byStore(s);
var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s), 16);
private Region createGraphic(DataStoreEntryRef<? extends FileSystemStore> s) {
var provider = s.get().getProvider();
var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s.getStore()), 16);
return new Label(getName(s), img.createRegion());
}
private Region createDisplayGraphic(FileSystemStore s) {
var provider = DataStoreProviders.byStore(s);
var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s), 16);
private Region createDisplayGraphic(DataStoreEntryRef<? extends FileSystemStore> s) {
var provider = s.get().getProvider();
var img = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s.getStore()), 16);
return new Label(null, img.createRegion());
}
@Override
protected Region createSimple() {
var comboBox = new CustomComboBoxBuilder<>(selected, this::createGraphic, null, v -> true);
comboBox.setAccessibleNames(store -> getName(store));
comboBox.setAccessibleNames(FileSystemStoreChoiceComp::getName);
comboBox.setSelectedDisplay(this::createDisplayGraphic);
DataStorage.get().getUsableStores().stream()
.filter(e -> e instanceof FileSystemStore)
.map(e -> (FileSystemStore) e)
DataStorage.get().getUsableEntries().stream()
.filter(e -> e.getStore() instanceof FileSystemStore)
.map(DataStoreEntry::<FileSystemStore>ref)
.forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("choice-comp");

View file

@ -12,7 +12,7 @@ import java.nio.file.Path;
import java.util.regex.Matcher;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class LocalFileReference {
public class ContextualFileReference {
private static String lastDataDir;
@ -36,7 +36,7 @@ public class LocalFileReference {
}
}
public static LocalFileReference of(String s) {
public static ContextualFileReference of(String s) {
if (s == null) {
return null;
}
@ -45,7 +45,7 @@ public class LocalFileReference {
// Replacement order is important
var replaced = ns.replace("<DATA>", getDataDir())
.replace("~", normalized(System.getProperty("user.home")));
return new LocalFileReference(normalized(replaced));
return new ContextualFileReference(normalized(replaced));
}
@NonNull

View file

@ -550,10 +550,9 @@ public abstract class DataStorage {
return children;
}
public List<DataStore> getUsableStores() {
public List<DataStoreEntry> getUsableEntries() {
return new ArrayList<>(getStoreEntries().stream()
.filter(entry -> entry.getValidity().isUsable())
.map(DataStoreEntry::getStore)
.toList());
}

View file

@ -19,25 +19,25 @@ public class StorageJacksonModule extends SimpleModule {
public void setupModule(SetupContext context) {
addSerializer(DataStoreEntryRef.class, new DataStoreEntryRefSerializer());
addDeserializer(DataStoreEntryRef.class, new DataStoreEntryRefDeserializer());
addSerializer(LocalFileReference.class, new LocalFileReferenceSerializer());
addDeserializer(LocalFileReference.class, new LocalFileReferenceDeserializer());
addSerializer(ContextualFileReference.class, new LocalFileReferenceSerializer());
addDeserializer(ContextualFileReference.class, new LocalFileReferenceDeserializer());
context.addSerializers(_serializers);
context.addDeserializers(_deserializers);
}
public static class LocalFileReferenceSerializer extends JsonSerializer<LocalFileReference> {
public static class LocalFileReferenceSerializer extends JsonSerializer<ContextualFileReference> {
@Override
public void serialize(LocalFileReference value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
public void serialize(ContextualFileReference value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.serialize());
}
}
public static class LocalFileReferenceDeserializer extends JsonDeserializer<LocalFileReference> {
public static class LocalFileReferenceDeserializer extends JsonDeserializer<ContextualFileReference> {
@Override
public LocalFileReference deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalFileReference.of(p.getValueAsString());
public ContextualFileReference deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return ContextualFileReference.of(p.getValueAsString());
}
}

View file

@ -1,7 +1,5 @@
package io.xpipe.app.test;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileStore;
import lombok.SneakyThrows;
import java.nio.file.Path;
@ -16,14 +14,4 @@ public class ExtensionTest {
}
return Path.of(url.toURI());
}
@SneakyThrows
public static DataStore getResourceStore(String name) {
var url = ExtensionTest.class.getClassLoader().getResource(name);
if (url == null) {
throw new IllegalArgumentException(String.format("File %s does not exist", name));
}
var file = Path.of(url.toURI()).toString();
return FileStore.local(Path.of(file));
}
}

View file

@ -0,0 +1,43 @@
package io.xpipe.app.util;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.JacksonizedValue;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.nio.file.Path;
/**
* Represents a file located on a file system.
*/
@JsonTypeName("file")
@SuperBuilder
@Jacksonized
@Getter
public class FileReference extends JacksonizedValue {
DataStoreEntryRef<? extends FileSystemStore> fileSystem;
String path;
public FileReference(DataStoreEntryRef<? extends FileSystemStore> fileSystem, String path) {
this.fileSystem = fileSystem;
this.path = path;
}
public static FileReference local(Path p) {
return new FileReference(DataStorage.get().local().ref(), p.toString());
}
public static FileReference local(String p) {
return new FileReference(DataStorage.get().local().ref(), p);
}
public final boolean isLocal() {
return fileSystem.getStore() instanceof LocalStore;
}
}

View file

@ -1,93 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.ValidationException;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.regex.Pattern;
/**
* Represents a file located on a file system.
*/
@JsonTypeName("file")
@SuperBuilder
@Jacksonized
@Getter
public class FileStore extends JacksonizedValue implements FilenameStore, StreamDataStore {
FileSystemStore fileSystem;
String path;
public FileStore(FileSystemStore fileSystem, String path) {
this.fileSystem = fileSystem;
this.path = path;
}
public static FileStore local(Path p) {
return new FileStore(new LocalStore(), p.toString());
}
/**
* Creates a file store for a file that is local to the callers machine.
*/
public static FileStore local(String p) {
return new FileStore(new LocalStore(), p);
}
public String getParent() {
var matcher = Pattern.compile("^(.+?)[^\\\\/]+$").matcher(path);
if (!matcher.matches()) {
throw new IllegalArgumentException("Unable to determine parent of " + path);
}
return matcher.group(1);
}
public final boolean isLocal() {
return fileSystem instanceof LocalStore;
}
@Override
public void checkComplete() throws Exception {
if (fileSystem == null) {
throw new ValidationException("File system is missing");
}
if (path == null) {
throw new ValidationException("File is missing");
}
if (!FileNames.isAbsolute(path)) {
throw new ValidationException("File path is not absolute");
}
}
@Override
public InputStream openInput() throws Exception {
return fileSystem.createFileSystem().open().openInput(path);
}
@Override
public OutputStream openOutput() throws Exception {
fileSystem.createFileSystem().open().mkdirs(getParent());
return fileSystem.createFileSystem().open().openOutput(path);
}
@Override
public boolean canOpen() throws Exception {
return fileSystem.createFileSystem().open().fileExists(path);
}
@Override
public String getFileName() {
var split = path.split("[\\\\/]");
if (split.length == 0) {
return "";
}
return split[split.length - 1];
}
}

View file

@ -4,7 +4,7 @@ import io.xpipe.api.DataSource;
import io.xpipe.app.test.DaemonExtensionTest;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.store.FileStore;
import io.xpipe.app.util.FileReference;
import io.xpipe.core.impl.TextSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@ -38,15 +38,15 @@ public class TextFileTest extends DaemonExtensionTest {
@BeforeAll
public static void setupStorage() throws Exception {
utf8 = getSource("text", "utf8-bom-lf.txt");
utf16 = DataSource.create(null, "text", FileStore.local(utf16File));
appendReference = DataSource.create(null, "text", FileStore.local(appendReferenceFile));
writeReference = DataSource.create(null, "text", FileStore.local(writeReferenceFile));
utf16 = DataSource.create(null, "text", FileReference.local(utf16File));
appendReference = DataSource.create(null, "text", FileReference.local(appendReferenceFile));
writeReference = DataSource.create(null, "text", FileReference.local(writeReferenceFile));
appendOutputFile = Files.createTempFile(null, null);
appendOutput = DataSource.create(
null,
TextSource.builder()
.store(FileStore.local(appendOutputFile))
.store(FileReference.local(appendOutputFile))
.charset(StreamCharset.get("windows-1252"))
.newLine(NewLine.LF)
.build());
@ -55,7 +55,7 @@ public class TextFileTest extends DaemonExtensionTest {
writeOutput = DataSource.create(
null,
TextSource.builder()
.store(FileStore.local(writeOutputFile))
.store(FileReference.local(writeOutputFile))
.charset(StreamCharset.UTF16_LE_BOM)
.newLine(NewLine.CRLF)
.build());
@ -90,7 +90,7 @@ public class TextFileTest extends DaemonExtensionTest {
var emptySource = DataSource.create(
null,
TextSource.builder()
.store(FileStore.local(empty))
.store(FileReference.local(empty))
.charset(StreamCharset.UTF32_BE)
.newLine(NewLine.CRLF)
.build());