More store rework

This commit is contained in:
crschnick 2023-06-29 22:57:47 +00:00
parent 9321af9998
commit 38cdf08bf2
117 changed files with 290 additions and 6924 deletions

View file

@ -2,25 +2,16 @@ package io.xpipe.api.impl;
import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.DataTable; import io.xpipe.api.DataTable;
import io.xpipe.api.connector.XPipeApiConnection;
import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.typed.TypedAbstractReader;
import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
import java.io.IOException; import java.util.ArrayList;
import java.io.InputStream; import java.util.Iterator;
import java.util.*; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTableImpl extends DataSourceImpl implements DataTable { public class DataTableImpl extends DataSourceImpl implements DataTable {
@ -34,9 +25,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
} }
public Stream<TupleNode> stream() { public Stream<TupleNode> stream() {
var iterator = new TableIterator(); return Stream.of();
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
.onClose(iterator::finish);
} }
@Override @Override
@ -52,71 +41,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
@Override @Override
public ArrayNode read(int maxRows) { public ArrayNode read(int maxRows) {
List<DataStructureNode> nodes = new ArrayList<>(); List<DataStructureNode> nodes = new ArrayList<>();
XPipeApiConnection.execute(con -> {
var req = QueryTableDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxRows(maxRows)
.build();
con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> {
var r = new TypedDataStreamParser(res.getDataType());
r.parseStructures(in, TypedDataStructureNodeReader.of(res.getDataType()), nodes::add);
});
});
return ArrayNode.of(nodes); return ArrayNode.of(nodes);
} }
@Override @Override
public Iterator<TupleNode> iterator() { public Iterator<TupleNode> iterator() {
return new TableIterator(); return new Iterator<TupleNode>() {
} @Override
public boolean hasNext() {
private class TableIterator implements Iterator<TupleNode> { return false;
private final BeaconConnection connection;
private final TypedDataStreamParser parser;
private final TypedAbstractReader nodeReader;
private TupleNode node;
{
connection = XPipeApiConnection.open();
var req = QueryTableDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxRows(Integer.MAX_VALUE)
.build();
connection.sendRequest(req);
QueryTableDataExchange.Response response = connection.receiveResponse();
nodeReader = TypedDataStructureNodeReader.of(response.getDataType());
parser = new TypedDataStreamParser(response.getDataType());
connection.receiveBody();
}
private void finish() {
connection.close();
}
@Override
public boolean hasNext() {
connection.checkClosed();
try {
node = (TupleNode) parser.parseStructure(connection.getInputStream(), nodeReader);
} catch (IOException e) {
throw new BeaconException(e);
} }
if (node == null) {
// finish(); @Override
public TupleNode next() {
return null;
} }
return node != null; };
}
@Override
public TupleNode next() {
connection.checkClosed();
return node;
}
} }
} }

View file

@ -2,25 +2,12 @@ package io.xpipe.api.impl;
import io.xpipe.api.DataSourceConfig; import io.xpipe.api.DataSourceConfig;
import io.xpipe.api.DataText; import io.xpipe.api.DataText;
import io.xpipe.api.connector.XPipeApiConnection;
import io.xpipe.beacon.BeaconConnection;
import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.DataSourceType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTextImpl extends DataSourceImpl implements DataText { public class DataTextImpl extends DataSourceImpl implements DataText {
@ -53,47 +40,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
@Override @Override
public Stream<String> lines() { public Stream<String> lines() {
var iterator = new Iterator<String>() { return Stream.of();
private final BeaconConnection connection;
private final BufferedReader reader;
private String nextValue;
{
connection = XPipeApiConnection.open();
var req = QueryTextDataExchange.Request.builder()
.ref(DataSourceReference.id(getId()))
.maxLines(-1)
.build();
connection.sendRequest(req);
connection.receiveResponse();
reader = new BufferedReader(new InputStreamReader(connection.receiveBody(), StandardCharsets.UTF_8));
}
private void close() {
connection.close();
}
@Override
public boolean hasNext() {
connection.checkClosed();
try {
nextValue = reader.readLine();
} catch (IOException e) {
throw new BeaconException(e);
}
return nextValue != null;
}
@Override
public String next() {
return nextValue;
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
.onClose(iterator::close);
} }
@Override @Override

View file

@ -6,7 +6,6 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BusyProperty; import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.TerminalHelper; import io.xpipe.app.util.TerminalHelper;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
@ -133,7 +132,7 @@ public final class OpenFileSystemModel {
&& fileSystem.getShell().isPresent()) { && fileSystem.getShell().isPresent()) {
var directory = currentPath.get(); var directory = currentPath.get();
var name = adjustedPath + " - " var name = adjustedPath + " - "
+ XPipeDaemon.getInstance().getStoreName(store).orElse("?"); + DataStorage.get().getStoreDisplayName(store).orElse("?");
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
if (ShellDialects.ALL.stream() if (ShellDialects.ALL.stream()
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) { .anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
@ -379,8 +378,7 @@ public final class OpenFileSystemModel {
var command = s.control() var command = s.control()
.initWith(connection.getShellDialect().getCdCommand(directory)) .initWith(connection.getShellDialect().getCdCommand(directory))
.prepareTerminalOpen(directory + " - " .prepareTerminalOpen(directory + " - "
+ XPipeDaemon.getInstance() + DataStorage.get().getStoreDisplayName(store)
.getStoreName(store)
.orElse("?")); .orElse("?"));
TerminalHelper.open(directory, command); TerminalHelper.open(directory, command);
} }

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -18,6 +19,9 @@ import java.util.function.Function;
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> { public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
private final ObservableList<T> shown; private final ObservableList<T> shown;
private final ObservableList<T> all; private final ObservableList<T> all;
private final Function<T, Comp<?>> compFunction; private final Function<T, Comp<?>> compFunction;
@ -65,6 +69,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
}) })
.toList(); .toList();
for (int i = 0; i < newShown.size(); i++) {
var r = newShown.get(i);
r.pseudoClassStateChanged(ODD, false);
r.pseudoClassStateChanged(EVEN, false);
r.pseudoClassStateChanged(i % 2 == 0 ? EVEN : ODD, true);
}
if (!listView.getChildren().equals(newShown)) { if (!listView.getChildren().equals(newShown)) {
listView.getChildren().setAll(newShown); listView.getChildren().setAll(newShown);
listView.layout(); listView.layout();

View file

@ -0,0 +1,42 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
public class SystemStateComp extends SimpleComp {
public SystemStateComp(ObservableValue<String> name, ObservableValue<State> state) {
this.name = name;
this.state = state;
}
public static enum State {
STOPPED,
RUNNING,
OTHER
}
private final ObservableValue<String> name;
private final ObservableValue<State> state;
@Override
protected Region createSimple() {
var icon = PlatformThread.sync(Bindings.createStringBinding(() -> {
return state.getValue() == State.STOPPED ? "mdmz-stop_circle" : state.getValue() == State.RUNNING ? "mdrmz-play_circle_outline" : "mdmz-remove_circle_outline";
}, state));
var fi = new FontIcon();
SimpleChangeListener.apply(icon, val -> fi.setIconLiteral(val));
new FancyTooltipAugment<>(PlatformThread.sync(name)).augment(fi);
var pane = new Pane(fi);
return pane;
}
}

View file

@ -1,101 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.util.CustomComboBoxBuilder;
import javafx.beans.property.Property;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
import java.util.function.Predicate;
public class DataSourceTargetChoiceComp extends Comp<CompStructure<ComboBox<Node>>> {
private final Property<DataSourceTarget> selectedApplication;
private final List<DataSourceTarget> all;
private DataSourceTargetChoiceComp(Property<DataSourceTarget> selectedApplication, List<DataSourceTarget> all) {
this.selectedApplication = selectedApplication;
this.all = all;
}
public static DataSourceTargetChoiceComp create(
Property<DataSourceTarget> selectedApplication, Predicate<DataSourceTarget> filter) {
selectedApplication.addListener((observable, oldValue, val) -> {
AppCache.update("application-last-used", val != null ? val.getId() : null);
});
var all = DataSourceTarget.getAll().stream().filter(filter).toList();
if (selectedApplication.getValue() == null) {
String selectedId = AppCache.get("application-last-used", String.class, () -> null);
var selectedProvider = selectedId != null
? DataSourceTarget.byId(selectedId).filter(filter).orElse(null)
: null;
selectedApplication.setValue(selectedProvider);
}
return new DataSourceTargetChoiceComp(selectedApplication, all);
}
private String getIconCode(DataSourceTarget p) {
return p.getGraphicIcon() != null
? p.getGraphicIcon()
: p.getCategory().equals(DataSourceTarget.Category.PROGRAMMING_LANGUAGE)
? "mdi2c-code-tags"
: "mdral-indeterminate_check_box";
}
private Region createLabel(DataSourceTarget p) {
var g = new FontIcon(getIconCode(p));
var l = new Label(p.getName().getValue(), g);
l.setAlignment(Pos.CENTER);
g.iconColorProperty().bind(l.textFillProperty());
return l;
}
@Override
public CompStructure<ComboBox<Node>> createBase() {
var addMoreLabel = new Label(AppI18n.get("addMore"), new FontIcon("mdmz-plus"));
var builder =
new CustomComboBoxBuilder<>(selectedApplication, app -> createLabel(app), new Label(""), v -> true);
builder.setAccessibleNames(
dataSourceTarget -> dataSourceTarget.getName().getValue());
// builder.addFilter((v, s) -> v.getName().getValue().toLowerCase().contains(s));
builder.addHeader(AppI18n.get("programmingLanguages"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.PROGRAMMING_LANGUAGE))
.forEach(builder::add);
builder.addHeader(AppI18n.get("applications"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.APPLICATION))
.forEach(builder::add);
builder.addHeader(AppI18n.get("other"));
all.stream()
.filter(p -> p.getCategory().equals(DataSourceTarget.Category.OTHER))
.forEach(builder::add);
// builder.addSeparator();
// builder.addAction(addMoreLabel, () -> {
//
// });
var cb = builder.build();
cb.getStyleClass().add("application-choice-comp");
cb.setMaxWidth(2000);
return new SimpleCompStructure<>(cb);
}
}

View file

@ -1,61 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.source.CollectionReadConnection;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.ArrayList;
public class DsCollectionComp extends Comp<CompStructure<TreeView<String>>> {
private final ObservableValue<CollectionReadConnection> con;
private final ObservableValue<String> value;
public DsCollectionComp(ObservableValue<CollectionReadConnection> con) {
this.con = con;
this.value = new SimpleObjectProperty<>("/");
}
private TreeItem<String> createTree() {
var c = new ArrayList<TreeItem<String>>();
if (con.getValue() != null) {
try {
con.getValue().listEntries().forEach(e -> {
// var item = new TreeItem<String>(e.getFileName());
// c.add(item);
});
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
var ar = new TreeItem<>(value.getValue());
ar.getChildren().setAll(c);
return ar;
}
private void setupListener(TreeView<String> tv) {
ChangeListener<CollectionReadConnection> listener = (c, o, n) -> {
var nt = createTree();
tv.setRoot(nt);
};
con.addListener(listener);
listener.changed(con, null, con.getValue());
}
@Override
public CompStructure<TreeView<String>> createBase() {
var table = new TreeView<String>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
}

View file

@ -1,203 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.DynamicOptionsComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.source.DataSourceId;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.List;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@AllArgsConstructor
@Getter
public class DsDataTransferComp extends SimpleComp {
private final Property<DataSourceEntry> dataSourceEntry;
Property<DataSourceTarget> selectedTarget = new SimpleObjectProperty<>();
Property<DataSourceTarget.InstructionsDisplay> selectedDisplay = new SimpleObjectProperty<>();
List<DataSourceTarget> excludedTargets = new ArrayList<>();
public DsDataTransferComp selectApplication(DataSourceTarget t) {
selectedTarget.setValue(t);
return this;
}
public DsDataTransferComp exclude(DataSourceTarget t) {
excludedTargets.add(t);
return this;
}
public static void showPipeWindow(DataSourceEntry e) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
AppWindowHelper.sideWindow(
AppI18n.get("pipeDataSource"),
window -> {
var ms = new DsDataTransferComp(new SimpleObjectProperty<>(e))
.exclude(DataSourceTarget.byId("base.saveSource")
.orElseThrow());
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();
}
@Override
public boolean canContinue() {
var selected = ms.selectedTarget.getValue();
if (selected == null) {
return false;
}
var validator = ms.selectedDisplay
.getValue()
.getValidator();
if (validator == null) {
return true;
}
return validator.validate();
}
}));
}
@Override
protected void finish() {
var onFinish = ms.getSelectedDisplay()
.getValue()
.getOnFinish();
if (onFinish != null) {
ThreadHelper.runAsync(() -> {
try (var busy = new BusyProperty(loading)) {
onFinish.run();
PlatformThread.runLaterIfNeeded(() -> window.close());
}
});
}
}
};
return multi.apply(s -> {
SimpleChangeListener.apply(ms.getSelectedTarget(), (c) -> {
if (c != null && c.getAccessType() == DataSourceTarget.AccessType.PASSIVE) {
((Region) s.get().getChildren().get(2)).setMaxHeight(0);
((Region) s.get().getChildren().get(2)).setMinHeight(0);
s.get().getChildren().get(2).setVisible(false);
} else {
((Region) s.get().getChildren().get(2)).setMaxHeight(Region.USE_PREF_SIZE);
((Region) s.get().getChildren().get(2)).setMinHeight(Region.USE_PREF_SIZE);
s.get().getChildren().get(2).setVisible(true);
}
});
s.get().setPrefWidth(600);
s.get().setPrefHeight(700);
AppFont.medium(s.get());
});
},
false,
loading)
.show();
});
}
@Override
public Region createSimple() {
ObservableValue<DataSourceId> id = Bindings.createObjectBinding(
() -> {
if (!DataStorage.get().getSourceEntries().contains(dataSourceEntry.getValue())) {
return null;
}
return DataStorage.get().getId(dataSourceEntry.getValue());
},
dataSourceEntry);
var chooser = DataSourceTargetChoiceComp.create(
selectedTarget,
a -> !excludedTargets.contains(a)
&& a.isApplicable(dataSourceEntry.getValue().getSource())
&& a.createRetrievalInstructions(
dataSourceEntry.getValue().getSource(), id)
!= null);
var setupGuideButton = new ButtonComp(
AppI18n.observable("setupGuide"), new FontIcon("mdoal-integration_instructions"), () -> {
Hyperlinks.open(selectedTarget.getValue().getSetupGuideURL());
})
.apply(s -> s.get()
.visibleProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return selectedTarget.getValue() != null
&& selectedTarget.getValue().getSetupGuideURL() != null;
},
selectedTarget)));
var top = new HorizontalComp(List.<Comp<?>>of(
chooser.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)), setupGuideButton))
.apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
struc.get().setSpacing(12);
struc.get().getStyleClass().add("top");
});
// setupGuideButton.prefHeightProperty().bind(chooserR.heightProperty());
var content = new VBox(
new DynamicOptionsComp(List.of(new DynamicOptionsComp.Entry(null, null, top)), false).createRegion(),
new Region());
SimpleChangeListener.apply(selectedTarget, c -> {
if (selectedTarget.getValue() == null) {
content.getChildren().set(1, new Region());
selectedDisplay.setValue(null);
return;
}
var instructions = selectedTarget
.getValue()
.createRetrievalInstructions(dataSourceEntry.getValue().getSource(), id);
content.getChildren().set(1, instructions.getRegion());
VBox.setVgrow(instructions.getRegion(), Priority.ALWAYS);
selectedDisplay.setValue(instructions);
});
content.setSpacing(15);
var r = content;
r.getStyleClass().add("data-source-retrieve");
return r;
}
}

View file

@ -1,88 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.*;
import io.xpipe.core.source.DataSourceType;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import lombok.Getter;
import net.synedra.validatorfx.Check;
import java.util.List;
public class DsProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>>> implements Validatable {
private final DataSourceProvider.Category type;
private final Property<DataSourceProvider<?>> provider;
@Getter
private final Validator validator = new SimpleValidator();
private final Check check;
private final DataSourceType filter;
public DsProviderChoiceComp(
DataSourceProvider.Category type, Property<DataSourceProvider<?>> provider, DataSourceType filter) {
this.type = type;
this.provider = provider;
check = Validator.nonNull(validator, AppI18n.observable("provider"), provider);
this.filter = filter;
}
private Region createDefaultNode() {
return switch (type) {
case STREAM -> JfxHelper.createNamedEntry(
AppI18n.get("anyStream"), AppI18n.get("anyStreamDescription"), "file_icon.png");
case DATABASE -> JfxHelper.createNamedEntry(
AppI18n.get("selectQueryType"), AppI18n.get("selectQueryTypeDescription"), "db_icon.png");
};
}
private List<DataSourceProvider<?>> getProviders() {
return switch (type) {
case STREAM -> DataSourceProviders.getAll().stream()
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get()
|| p.getCategory() == DataSourceProvider.Category.STREAM)
.filter(p -> p.shouldShow(filter))
.toList();
case DATABASE -> DataSourceProviders.getAll().stream()
.filter(p -> p.getCategory() == DataSourceProvider.Category.DATABASE)
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.shouldShow(filter))
.toList();
};
}
private Region createGraphic(DataSourceProvider<?> provider) {
if (provider == null) {
return createDefaultNode();
}
var graphic = provider.getDisplayIconFileName();
return JfxHelper.createNamedEntry(provider.getDisplayName(), provider.getDisplayDescription(), graphic);
}
@Override
public CompStructure<ComboBox<Node>> createBase() {
var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true);
comboBox.setAccessibleNames(
dataSourceProvider -> dataSourceProvider != null ? dataSourceProvider.getDisplayName() : null);
comboBox.add(null);
comboBox.addSeparator();
comboBox.addFilter((v, s) -> v.getDisplayName().toLowerCase().contains(s.toLowerCase()));
getProviders().forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
check.decorates(cb);
cb.getStyleClass().add("data-source-type");
cb.getStyleClass().add("choice-comp");
return new SimpleCompStructure<>(cb);
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
import java.util.HexFormat;
public class DsRawComp extends Comp<CompStructure<TextArea>> {
private final ObservableValue<byte[]> value;
public DsRawComp(ObservableValue<byte[]> value) {
this.value = value;
}
private void setupListener(TextArea ta) {
var format = HexFormat.of().withDelimiter(" ").withUpperCase();
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
ta.textProperty().setValue(format.formatHex(val));
});
}
@Override
public CompStructure<TextArea> createBase() {
var ta = new TextArea();
ta.setWrapText(true);
setupListener(ta);
return new SimpleCompStructure<>(ta);
}
}

View file

@ -1,44 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
public class DsStorageGroupSelector extends SimpleComp {
private final Property<DataSourceCollection> selected;
public DsStorageGroupSelector(Property<DataSourceCollection> selected) {
this.selected = selected;
}
private static Region createGraphic(DataSourceCollection group) {
if (group == null) {
return new Label("<>");
}
var l = new Label(group.getName());
return l;
}
@Override
protected ComboBox<Node> createSimple() {
var comboBox = new CustomComboBoxBuilder<>(
selected, DsStorageGroupSelector::createGraphic, createGraphic(null), v -> true);
comboBox.setAccessibleNames(dataSourceCollection -> dataSourceCollection.getName());
DataStorage.get().getSourceCollections().stream()
.filter(dataSourceCollection ->
!dataSourceCollection.equals(DataStorage.get().getInternalCollection()))
.forEach(comboBox::add);
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("storage-group-selector");
return cb;
}
}

View file

@ -1,75 +0,0 @@
package io.xpipe.app.comp.source;
import com.jfoenix.controls.JFXTextField;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.core.source.DataSourceId;
import javafx.beans.property.Property;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.util.List;
public class DsStorageTargetComp extends SimpleComp {
private final Property<DataSourceEntry> dataSourceEntry;
private final Property<DataSourceCollection> storageGroup;
private final Property<Boolean> nameValid;
public DsStorageTargetComp(
Property<DataSourceEntry> dataSourceEntry,
Property<DataSourceCollection> storageGroup,
Property<Boolean> nameValid) {
this.dataSourceEntry = dataSourceEntry;
this.storageGroup = storageGroup;
this.nameValid = nameValid;
}
@Override
protected Region createSimple() {
var type = new DataSourceTypeComp(
dataSourceEntry.getValue().getDataSourceType(),
dataSourceEntry.getValue().getSource().getFlow());
type.apply(struc -> struc.get().prefWidthProperty().bind(struc.get().prefHeightProperty()));
type.apply(struc -> struc.get().setPrefHeight(60));
var storageGroupSelector = new DsStorageGroupSelector(storageGroup).apply(s -> {
s.get().setMaxWidth(1000);
HBox.setHgrow(s.get(), Priority.ALWAYS);
});
var splitter = Comp.of(() -> new Label("" + DataSourceId.SEPARATOR)).apply(s -> {});
var name = Comp.of(() -> {
var nameField = new JFXTextField(dataSourceEntry.getValue().getName());
dataSourceEntry.addListener((c, o, n) -> {
nameField.setText(n.getName());
nameValid.setValue(n.getName().trim().length() > 0);
});
nameField.textProperty().addListener((c, o, n) -> {
dataSourceEntry.getValue().setName(n);
});
return nameField;
})
.apply(s -> HBox.setHgrow(s.get(), Priority.ALWAYS));
var right = new HorizontalComp(List.of(storageGroupSelector, splitter, name))
.apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
HBox.setHgrow(struc.get(), Priority.ALWAYS);
})
.styleClass("data-source-id");
return new HorizontalComp(List.of(type, right))
.apply(s -> s.get().setFillHeight(true))
.styleClass("data-source-preview")
.createRegion();
}
}

View file

@ -1,66 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.core.data.node.DataStructureNode;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class DsStructureComp extends Comp<CompStructure<TreeView<String>>> {
private final ObservableValue<DataStructureNode> value;
public DsStructureComp(ObservableValue<DataStructureNode> value) {
this.value = value;
}
private TreeItem<String> createTree(DataStructureNode n, AtomicInteger counter, int max) {
if (n.isArray()) {
var c = new ArrayList<TreeItem<String>>();
for (int i = 0; i < Math.min(n.size(), max - counter.get()); i++) {
var item = createTree(n.at(i), counter, max);
item.setValue("[" + i + "] = " + item.getValue());
c.add(item);
}
var ar = new TreeItem<>("[" + n.size() + "... ]");
ar.getChildren().setAll(c);
return ar;
} else if (n.isTuple()) {
var c = new ArrayList<TreeItem<String>>();
for (int i = 0; i < Math.min(n.size(), max - counter.get()); i++) {
var item = createTree(n.at(i), counter, max);
var key = n.asTuple().getKeyNames().get(i);
item.setValue((key != null ? key : "" + i) + " = " + item.getValue());
c.add(item);
}
var ar = new TreeItem<>("( " + n.size() + "... )");
ar.getChildren().setAll(c);
return ar;
} else {
var ar = new TreeItem<>(n.asValue().asString());
return ar;
}
}
private void setupListener(TreeView<String> tv) {
ChangeListener<DataStructureNode> listener = (c, o, n) -> {
var nt = createTree(n, new AtomicInteger(0), 100);
tv.setRoot(nt);
};
value.addListener(listener);
listener.changed(value, null, value.getValue());
}
@Override
public CompStructure<TreeView<String>> createBase() {
var table = new TreeView<String>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
}

View file

@ -1,93 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.type.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.util.ArrayList;
import java.util.Stack;
public class DsTableComp extends Comp<CompStructure<TableView<DsTableComp.RowWrapper>>> {
private final ObservableValue<ArrayNode> value;
public DsTableComp(ObservableValue<ArrayNode> value) {
this.value = value;
}
private TupleType determineDataType(ArrayNode table) {
if (table == null || table.size() == 0) {
return TupleType.empty();
}
var first = table.at(0);
return (TupleType) first.determineDataType();
}
private void setupListener(TableView<RowWrapper> table) {
SimpleChangeListener.apply(PlatformThread.sync(value), n -> {
table.getItems().clear();
table.getColumns().clear();
var t = determineDataType(n);
var stack = new Stack<ObservableList<TableColumn<RowWrapper, ?>>>();
stack.push(table.getColumns());
t.visit(DataTypeVisitors.table(
tupleName -> {
var current = stack.peek();
stack.push(current.get(current.size() - 1).getColumns());
},
stack::pop,
(name, pointer) -> {
TableColumn<RowWrapper, String> col = new TableColumn<>(name);
col.setCellValueFactory(cellData -> {
var node = pointer.get(n.at(cellData.getValue().rowIndex()));
return new SimpleStringProperty(nodeToString(node));
});
var current = stack.peek();
current.add(col);
}));
var list = new ArrayList<RowWrapper>(n.size());
for (int i = 0; i < n.size(); i++) {
list.add(new RowWrapper(n, i));
}
table.setItems(FXCollections.observableList(list));
});
}
private String nodeToString(DataStructureNode node) {
if (node.isValue()) {
return node.asString();
}
if (node.isArray()) {
return "[...]";
}
if (node.isTuple()) {
return "{...}";
}
return null;
}
@Override
public CompStructure<TableView<RowWrapper>> createBase() {
var table = new TableView<RowWrapper>();
setupListener(table);
return new SimpleCompStructure<>(table);
}
public record RowWrapper(ArrayNode table, int rowIndex) {}
}

View file

@ -1,49 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.core.source.TableMapping;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public class DsTableMappingComp extends SimpleComp {
ObservableValue<TableMapping> mapping;
public DsTableMappingComp(ObservableValue<TableMapping> mapping) {
this.mapping = PlatformThread.sync(mapping);
}
@Override
protected Region createSimple() {
var grid = new GridPane();
grid.getStyleClass().add("table-mapping-comp");
SimpleChangeListener.apply(mapping, val -> {
for (int i = 0; i < val.getInputType().getSize(); i++) {
var input = new LabelComp(val.getInputType().getNames().get(i));
grid.add(input.createRegion(), 0, i);
grid.add(new LabelComp("->").createRegion(), 1, i);
var map = val.map(i).orElse(-1);
var output =
new LabelComp(map != -1 ? val.getOutputType().getNames().get(map) : AppI18n.get("discarded"));
grid.add(output.createRegion(), 2, i);
if (i % 2 != 0) {
grid.getChildren().stream().skip((i * 3L)).forEach(node -> node.getStyleClass()
.add("odd"));
}
}
});
return grid;
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextArea;
import java.util.List;
public class DsTextComp extends Comp<CompStructure<TextArea>> {
private final ObservableValue<List<String>> value;
public DsTextComp(ObservableValue<List<String>> value) {
this.value = value;
}
private void setupListener(TextArea ta) {
ChangeListener<List<String>> listener = (c, o, n) -> {
ta.textProperty().setValue(String.join("\n", n));
};
value.addListener(listener);
listener.changed(value, null, value.getValue());
}
@Override
public CompStructure<TextArea> createBase() {
var ta = new TextArea();
setupListener(ta);
return new SimpleCompStructure<>(ta);
}
}

View file

@ -1,83 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.Arrays;
public class DsTypeChoiceComp extends Comp<CompStructure<StackPane>> {
private final ObservableValue<? extends DataSource<?>> baseSource;
private final ObservableValue<DataSourceProvider<?>> provider;
private final Property<DataSourceType> selectedType;
public DsTypeChoiceComp(
ObservableValue<? extends DataSource<?>> baseSource,
ObservableValue<DataSourceProvider<?>> provider,
Property<DataSourceType> selectedType) {
this.baseSource = baseSource;
this.provider = provider;
this.selectedType = selectedType;
}
private Region createLabel(DataSourceType p) {
var l = new Label(AppI18n.get(p.name().toLowerCase()), new FontIcon(DataSourceTypeComp.ICONS.get(p)));
l.setAlignment(Pos.CENTER);
return l;
}
@Override
public CompStructure<StackPane> createBase() {
var sp = new StackPane();
Runnable update = () -> {
sp.getChildren().clear();
if (provider.getValue() == null || baseSource.getValue() == null) {
return;
}
var builder = new CustomComboBoxBuilder<>(selectedType, app -> createLabel(app), new Label(""), v -> true);
builder.setAccessibleNames(dataSourceType -> dataSourceType.toString());
builder.add(provider.getValue().getPrimaryType());
var list = Arrays.stream(DataSourceType.values())
.filter(t -> t != provider.getValue().getPrimaryType()
&& provider.getValue()
.supportsConversion(baseSource.getValue().asNeeded(), t))
.toList();
if (list.size() == 0) {
return;
}
list.forEach(t -> builder.add(t));
var cb = builder.build();
cb.getStyleClass().add("data-source-type-choice-comp");
sp.getChildren().add(cb);
};
baseSource.addListener((c, o, n) -> {
update.run();
});
provider.addListener((c, o, n) -> {
update.run();
});
update.run();
return new SimpleCompStructure<>(sp);
}
}

View file

@ -1,278 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.source.*;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GuiDsConfigStep extends MultiStepComp.Step<CompStructure<?>> {
private final Property<? extends DataStore> input;
private final ObservableValue<? extends DataSource<?>> baseSource;
private final Property<? extends DataSource<?>> currentSource;
private final Property<DataSourceProvider<?>> provider;
private final Property<DataSourceType> type;
private final Map<DataSourceType, Comp<?>> previewComps;
private final BooleanProperty loading;
public GuiDsConfigStep(
Property<DataSourceProvider<?>> provider,
Property<? extends DataStore> input,
Property<? extends DataSource<?>> baseSource,
Property<? extends DataSource<?>> currentSource,
Property<DataSourceType> type,
BooleanProperty loading) {
this.input = input;
this.baseSource = baseSource;
this.currentSource = currentSource;
this.type = type;
this.provider = provider;
this.loading = loading;
this.previewComps = new HashMap<>();
Arrays.stream(DataSourceType.values()).forEach(t -> previewComps.put(t, createPreviewComp(t)));
}
@Override
public CompStructure<?> createBase() {
var hide = Bindings.createBooleanBinding(
() -> {
return currentSource.getValue() != null
&& currentSource.getValue().getFlow() == DataFlow.OUTPUT;
},
currentSource);
var top = new HorizontalComp(List.of(createTypeSelectorComp(), Comp.of(Region::new), createRefreshComp()))
.apply(s -> {
HBox.setHgrow(s.get().getChildren().get(1), Priority.ALWAYS);
s.get().setAlignment(Pos.CENTER);
s.get().setSpacing(7);
})
.hide(hide);
var preview = Bindings.createObjectBinding(
() -> {
return previewComps.get(type.getValue()).hide(hide);
},
type);
var layout = new VerticalComp(List.of(top, preview.getValue(), Comp.of(this::createConfigOptions)));
layout.apply(vbox -> {
vbox.get().setAlignment(Pos.CENTER);
VBox.setVgrow(vbox.get().getChildren().get(1), Priority.ALWAYS);
AppFont.small(vbox.get());
currentSource.addListener((c, o, n) -> {
vbox.get().getChildren().set(1, preview.getValue().createRegion());
VBox.setVgrow(vbox.get().getChildren().get(1), Priority.ALWAYS);
});
provider.addListener((c, o, n) -> {
vbox.get().getChildren().set(2, createConfigOptions());
});
})
.styleClass("data-source-config");
provider.addListener((c, o, n) -> {});
return layout.createStructure();
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Region createConfigOptions() {
if (currentSource.getValue() == null || provider.getValue() == null) {
return new Region();
}
Region r = null;
try {
r = ((DataSourceProvider<T>) provider.getValue()).configGui((Property<T>) baseSource, false);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
return r != null ? r : new Region();
}
private Comp<?> createReportComp() {
return new IconButtonComp("mdi2a-alert-circle-outline", () -> {});
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Comp<?> createRefreshComp() {
return new IconButtonComp("mdmz-refresh", () -> {
T src = currentSource.getValue() != null
? currentSource.getValue().asNeeded()
: null;
currentSource.setValue(null);
((Property<T>) currentSource).setValue(src);
})
.shortcut(new KeyCodeCombination(KeyCode.F5));
}
private Comp<?> createTypeSelectorComp() {
return new DsTypeChoiceComp(baseSource, provider, type);
}
private Comp<?> createPreviewComp(DataSourceType t) {
return switch (t) {
case TABLE -> createTablePreviewComp();
case STRUCTURE -> createStructurePreviewComp();
case TEXT -> createTextPreviewComp();
case RAW -> createRawPreviewComp();
case COLLECTION -> createCollectionPreviewComp();
};
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends TextDataSource<DI>> Comp<?> createTextPreviewComp() {
var text = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.TEXT) {
return List.of("");
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.lines().limit(10).toList();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return List.of("");
}
},
currentSource);
return new DsTextComp(text);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends StructureDataSource<DI>> Comp<?> createStructurePreviewComp() {
var structure = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.STRUCTURE) {
return TupleNode.builder().build();
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.read();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return TupleNode.builder().build();
}
},
currentSource);
return new DsStructureComp(structure);
}
@Override
public void onInit() {
// DataSource<?> src = currentSource.getValue() != null ? currentSource.getValue().asNeeded() : null;
// currentSource.setValue(null);
// currentSource.setValue(src != null ? src.asNeeded() : null);
}
private Comp<?> createTablePreviewComp() {
var table = new SimpleObjectProperty<>(ArrayNode.of());
currentSource.addListener((c, o, val) -> {
ThreadHelper.runAsync(() -> {
if (val == null
|| type.getValue() != DataSourceType.TABLE
|| !val.getFlow().hasInput()) {
return;
}
try (var ignored = new BusyProperty(loading);
var con = (TableReadConnection) val.openReadConnection()) {
con.init();
table.set(con.readRows(50));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
table.set(ArrayNode.of());
}
});
});
return new DsTableComp(table);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends RawDataSource<DI>> Comp<?> createRawPreviewComp() {
var bytes = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.RAW) {
return new byte[0];
}
try (var con = ((DS) currentSource.getValue()).openReadConnection()) {
con.init();
return con.readBytes(1000);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return new byte[0];
}
},
currentSource);
return new DsRawComp(bytes);
}
@SuppressWarnings("unchecked")
private <DI extends DataStore, DS extends CollectionDataSource<DI>> Comp<?> createCollectionPreviewComp() {
var con = Bindings.createObjectBinding(
() -> {
if (currentSource.getValue() == null || type.getValue() != DataSourceType.COLLECTION) {
return null;
}
/*
TODO: Fix
*/
try {
return ((DS) currentSource.getValue()).openReadConnection();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
return null;
}
},
currentSource,
input);
con.addListener((c, o, n) -> {
if (o == null) {
return;
}
try {
o.close();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
return new DsCollectionComp(con);
}
}

View file

@ -1,294 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.scene.control.Alert;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public class GuiDsCreatorMultiStep<DI extends DataStore, DS extends DataSource<DI>> extends MultiStepComp {
private final Stage window;
private final DataSourceEntry editing;
private final DataSourceCollection targetGroup;
private final Property<DataSourceType> dataSourceType;
private final DataSourceProvider.Category category;
private final Property<DataSourceProvider<?>> provider;
private final Property<DI> store;
private final ObjectProperty<DS> baseSource;
private final ObjectProperty<DS> source;
private final BooleanProperty loading = new SimpleBooleanProperty();
private final State state;
private GuiDsCreatorMultiStep(
Stage window,
DataSourceEntry editing,
DataSourceCollection targetGroup,
DataSourceProvider.Category category,
DataSourceProvider<?> provider,
DI store,
DS source,
State state) {
this.window = window;
this.editing = editing;
this.targetGroup = targetGroup;
this.category = category;
this.provider = new SimpleObjectProperty<>(provider);
this.store = new SimpleObjectProperty<>(store);
this.dataSourceType = new SimpleObjectProperty<>(provider != null ? provider.getPrimaryType() : null);
this.baseSource = new SimpleObjectProperty<>(source);
this.source = new SimpleObjectProperty<>(source);
this.state = state;
addListeners();
this.apply(r -> {
r.get().setPrefWidth(AppFont.em(30));
r.get().setPrefHeight(AppFont.em(35));
});
}
public static void showCreation(DataSourceProvider.Category category, DataSourceCollection sourceCollection) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
var stage = AppWindowHelper.sideWindow(
AppI18n.get("newDataSource"),
window -> {
var ms = new GuiDsCreatorMultiStep<>(
window, null, sourceCollection, category, null, null, null, State.CREATE);
loading.bind(ms.loading);
window.setOnCloseRequest(e -> {
if (ms.state == State.CREATE && ms.source.getValue() != null) {
e.consume();
showCloseConfirmAlert(ms, window);
}
});
return ms;
},
false,
loading);
stage.show();
});
}
public static void showEdit(DataSourceEntry entry) {
Platform.runLater(() -> {
var loading = new SimpleBooleanProperty();
var stage = AppWindowHelper.sideWindow(
AppI18n.get("editDataSource"),
window -> {
var ms = new GuiDsCreatorMultiStep<>(
window,
entry,
DataStorage.get()
.getCollectionForSourceEntry(entry)
.orElse(null),
entry.getProvider().getCategory(),
entry.getProvider(),
entry.getStore().asNeeded(),
entry.getSource().asNeeded(),
State.EDIT);
loading.bind(ms.loading);
return ms.apply(struc -> ms.next());
},
false,
loading);
stage.show();
});
}
public static Future<Boolean> showForStore(
DataSourceProvider.Category category, DataStore store, DataSourceCollection sourceCollection) {
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
var provider = DataSourceProviders.byPreferredStore(store, null);
Platform.runLater(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("newDataSource"),
window -> {
var gui = new GuiDsCreatorMultiStep<>(
window,
null,
sourceCollection,
category,
provider.orElse(null),
store,
null,
State.CREATE);
gui.completedProperty().addListener((c, o, n) -> {
if (n) {
completableFuture.complete(true);
}
});
window.setOnCloseRequest(e -> {
if (gui.state == State.CREATE && gui.source.getValue() != null) {
e.consume();
showCloseConfirmAlert(gui, window);
}
});
return gui;
},
false,
null);
stage.show();
stage.setOnHiding(e -> {
completableFuture.complete(false);
});
});
return completableFuture;
}
private static void showCloseConfirmAlert(GuiDsCreatorMultiStep<?, ?> ms, Stage s) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmDsCreationAbortTitle"));
alert.setHeaderText(AppI18n.get("confirmDsCreationAbortHeader"));
alert.setContentText(AppI18n.get("confirmDsCreationAbortContent"));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
s.close();
});
}
@SuppressWarnings("unchecked")
private void addListeners() {
this.provider.addListener((c, o, n) -> {
if (n == null) {
this.dataSourceType.setValue(null);
return;
}
if (baseSource.getValue() != null
&& !n.getSourceClass().equals(baseSource.get().getClass())) {
this.baseSource.setValue(null);
}
this.dataSourceType.setValue(n.getPrimaryType());
});
this.store.addListener((c, o, n) -> {
if (n == null) {
return;
}
if (this.provider.getValue() == null) {
this.provider.setValue(
DataSourceProviders.byPreferredStore(n, null).orElse(null));
if (this.provider.getValue() != null) {
try {
this.baseSource.set((DS) provider.getValue().createDefaultSource(n));
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
}
});
this.baseSource.addListener((c, o, n) -> {
if (n == null) {
this.source.set(null);
return;
}
try {
var converted = dataSourceType.getValue() != provider.getValue().getPrimaryType()
? (provider.getValue()).convert(n.asNeeded(), dataSourceType.getValue())
: n;
source.setValue((DS) converted);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
this.dataSourceType.addListener((c, o, n) -> {
if (n == null || source.get() == null) {
return;
}
if (n == this.provider.getValue().getPrimaryType()) {
this.source.set(baseSource.getValue());
return;
}
try {
var conv = this.provider.getValue().convert(baseSource.get().asNeeded(), n);
this.source.set(conv.asNeeded());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
}
@Override
protected Region createStepOverview(Region content) {
var r = super.createStepOverview(content);
AppFont.small(r);
return r;
}
@Override
protected Region createStepNavigation() {
var r = super.createStepNavigation();
AppFont.small(r);
return r;
}
@Override
protected List<Entry> setup() {
var list = new ArrayList<Entry>();
list.add(new Entry(AppI18n.observable("selectInput"), createInputStep()));
list.add(new Entry(
AppI18n.observable("configure"),
new GuiDsConfigStep(provider, store, baseSource, source, dataSourceType, loading)));
switch (state) {
case EDIT -> {}
case CREATE -> {
list.add(new Entry(
AppI18n.observable("target"), new GuiDsCreatorTransferStep(targetGroup, store, source)));
}
}
return list;
}
@SuppressWarnings("unchecked")
private MultiStepComp.Step<?> createInputStep() {
return new GuiDsStoreSelectStep(
this, provider, (ObjectProperty<DataStore>) store, category, baseSource, loading);
}
@Override
protected void finish() {
switch (state) {
case EDIT -> {
editing.setSource(source.get());
}
case CREATE -> {}
}
window.close();
}
public enum State {
EDIT,
CREATE
}
}

View file

@ -1,72 +0,0 @@
package io.xpipe.app.comp.source;
import com.jfoenix.controls.JFXCheckBox;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Label;
import java.util.ArrayList;
import java.util.List;
public class GuiDsCreatorSaveStep extends MultiStepComp.Step<CompStructure<?>> {
private final Property<DataSourceCollection> storageGroup;
private final Property<DataSourceEntry> dataSourceEntry;
private final Property<Boolean> nameValid = new SimpleObjectProperty<>(true);
private final Property<Boolean> storeForLaterUse = new SimpleBooleanProperty(true);
public GuiDsCreatorSaveStep(
Property<DataSourceCollection> storageGroup, Property<DataSourceEntry> dataSourceEntry) {
this.storageGroup = storageGroup;
this.dataSourceEntry = dataSourceEntry;
}
@Override
public CompStructure<?> createBase() {
var storeSwitch = Comp.of(() -> {
var cb = new JFXCheckBox();
cb.selectedProperty().bindBidirectional(storeForLaterUse);
var label = new Label(AppI18n.get("storeForLaterUse"));
label.setGraphic(cb);
return label;
});
var storeSettings = new VerticalComp(List.of(new DsStorageTargetComp(dataSourceEntry, storageGroup, nameValid)))
.apply(struc -> {
var elems = new ArrayList<>(struc.get().getChildren());
if (!storeForLaterUse.getValue()) {
struc.get().getChildren().clear();
}
storeForLaterUse.addListener((c, o, n) -> {
if (n) {
struc.get().getChildren().addAll(elems);
} else {
struc.get().getChildren().clear();
}
});
})
.styleClass("store-options");
var vert = new VerticalComp(List.of(storeSwitch, storeSettings));
vert.styleClass("data-source-save-step");
vert.apply(r -> AppFont.small(r.get()));
return vert.createStructure();
}
@Override
public boolean canContinue() {
return nameValid.getValue();
}
}

View file

@ -1,112 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.ext.DataSourceTarget;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import java.util.List;
import java.util.UUID;
public class GuiDsCreatorTransferStep extends MultiStepComp.Step<CompStructure<?>> {
private final DataSourceCollection targetGroup;
private final Property<? extends DataStore> store;
private final ObjectProperty<? extends DataSource<?>> source;
private final Property<DataSourceEntry> entry = new SimpleObjectProperty<>();
private DsDataTransferComp comp;
public GuiDsCreatorTransferStep(
DataSourceCollection targetGroup,
Property<? extends DataStore> store,
ObjectProperty<? extends DataSource<?>> source) {
this.targetGroup = targetGroup;
this.store = store;
this.source = source;
entry.bind(Bindings.createObjectBinding(
() -> {
if (this.store.getValue() == null || this.source.get() == null) {
return null;
}
var name = DataStorage.get()
.createUniqueSourceEntryName(DataStorage.get().getInternalCollection(), source.get());
var entry = DataSourceEntry.createNew(UUID.randomUUID(), name, this.source.get());
return entry;
},
this.store,
this.source));
}
@Override
public CompStructure<?> createBase() {
comp = new DsDataTransferComp(entry)
.selectApplication(
targetGroup != null
? DataSourceTarget.byId("base.saveSource").orElseThrow()
: null);
var vert = new VerticalComp(List.of(comp.apply(s -> VBox.setVgrow(s.get(), Priority.ALWAYS))));
vert.styleClass("data-source-finish-step");
vert.apply(r -> AppFont.small(r.get()));
return Comp.derive(vert, vBox -> {
var r = new ScrollPane(vBox);
r.setFitToWidth(true);
return r;
})
.createStructure();
}
@Override
public void onInit() {
var e = entry.getValue();
DataStorage.get().add(e, DataStorage.get().getInternalCollection());
}
@Override
public void onBack() {
var e = entry.getValue();
DataStorage.get().deleteSourceEntry(e);
}
@Override
public void onContinue() {
var onFinish = comp.getSelectedDisplay().getValue().getOnFinish();
if (onFinish != null) {
onFinish.run();
}
var e = entry.getValue();
DataStorage.get().deleteSourceEntry(e);
}
@Override
public boolean canContinue() {
var selected = comp.getSelectedTarget().getValue();
if (selected == null) {
return false;
}
var validator = comp.getSelectedDisplay().getValue().getValidator();
if (validator == null) {
return true;
}
return validator.validate();
}
}

View file

@ -1,123 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.source.store.DsDbStoreChooserComp;
import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.scene.control.Separator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.List;
public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? extends Region>> {
private final MultiStepComp parent;
private final Property<DataSourceProvider<?>> provider;
private final Property<DataStore> input;
private final DataSourceProvider.Category category;
private final ObjectProperty<? extends DataSource<?>> baseSource;
private final BooleanProperty loading;
public GuiDsStoreSelectStep(
MultiStepComp parent,
Property<DataSourceProvider<?>> provider,
Property<DataStore> input,
DataSourceProvider.Category category,
ObjectProperty<? extends DataSource<?>> baseSource,
BooleanProperty loading) {
this.parent = parent;
this.provider = provider;
this.input = input;
this.category = category;
this.baseSource = baseSource;
this.loading = loading;
}
private Region createLayout() {
var layout = new BorderPane();
var providerChoice = new DsProviderChoiceComp(category, provider, null);
providerChoice.apply(GrowAugment.create(true, false));
layout.setCenter(createCategoryChooserComp());
var top = new VBox(providerChoice.createRegion(), new Separator());
top.getStyleClass().add("top");
layout.setTop(top);
layout.getStyleClass().add("data-input-creation-step");
return layout;
}
private Region createCategoryChooserComp() {
if (category == DataSourceProvider.Category.STREAM) {
return new DsStreamStoreChoiceComp(input, provider, true, true, DsStreamStoreChoiceComp.Mode.OPEN)
.createRegion();
}
if (category == DataSourceProvider.Category.DATABASE) {
return new DsDbStoreChooserComp(input, provider).createRegion();
}
throw new AssertionError();
}
@Override
public CompStructure<? extends Region> createBase() {
// var bgImg = AppImages.image("plus_bg.jpg");
// var background = new BackgroundImageComp(bgImg)
// .apply(struc -> struc.get().setOpacity(0.1));
var layered = new StackComp(List.of(Comp.of(this::createLayout)));
return layered.createStructure();
}
@Override
public void onContinue() {}
@Override
public boolean canContinue() {
if (input.getValue() == null || provider.getValue() == null) {
return false;
}
if (baseSource.getValue() != null) {
return true;
}
ThreadHelper.runAsync(() -> {
try (var ignored = new BusyProperty(loading)) {
var n = this.input.getValue();
var ds = this.provider.getValue().createDefaultSource(n);
if (ds == null) {
TrackEvent.warn("Default data source is null");
return;
}
PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(ds.asNeeded());
parent.next();
});
} catch (Exception e) {
ErrorEvent.fromThrowable(e).build().handle();
PlatformThread.runLaterIfNeededBlocking(() -> {
baseSource.setValue(null);
});
}
});
return false;
}
}

View file

@ -1,107 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.source.TableMapping;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@Value
@EqualsAndHashCode(callSuper = true)
public class GuiDsTableMappingConfirmation extends SimpleComp {
ObservableValue<TableMapping> mapping;
public GuiDsTableMappingConfirmation(ObservableValue<TableMapping> mapping) {
this.mapping = PlatformThread.sync(mapping);
}
public static boolean showWindowAndWait(SourceEntryWrapper source, TableMapping mapping) {
var latch = new CountDownLatch(1);
var confirmed = new AtomicBoolean();
AppWindowHelper.showAndWaitForWindow(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("confirmTableMappingTitle"),
window -> {
var ms = new GuiDsTableMappingConfirmation(new SimpleObjectProperty<>(mapping));
var multi = new MultiStepComp() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(null, new Step<>() {
@Override
public CompStructure<?> createBase() {
return ms.createStructure();
}
}));
}
@Override
protected void finish() {
confirmed.set(true);
window.close();
}
};
return multi.apply(s -> {
s.get().setPrefWidth(400);
s.get().setPrefHeight(500);
AppFont.medium(s.get());
});
},
false,
null);
stage.setOnHidden(event -> latch.countDown());
return stage;
});
return confirmed.get();
}
@Override
protected Region createSimple() {
var header = new LabelComp(AppI18n.observable("confirmTableMapping"))
.apply(struc -> struc.get().setWrapText(true));
var content = Comp.derive(new DsTableMappingComp(mapping), region -> {
var box = new HBox(region);
box.setAlignment(Pos.CENTER);
box.getStyleClass().add("grid-container");
return box;
})
.apply(struc -> AppFont.normal(struc.get()));
var changeNotice = new LabelComp(AppI18n.observable("changeTableMapping"))
.apply(struc -> struc.get().setWrapText(true));
var changeButton = Comp.of(() -> {
var hl = new Hyperlink("Customizing Data Flows");
hl.setOnAction(e -> {});
hl.setMaxWidth(250);
return hl;
});
return new VerticalComp(List.of(
header,
content,
Comp.of(() -> new Separator(Orientation.HORIZONTAL)),
changeNotice,
changeButton))
.styleClass("table-mapping-confirmation-comp")
.createRegion();
}
}

View file

@ -1,146 +0,0 @@
package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.ListViewComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.SimpleValidator;
import io.xpipe.app.util.Validatable;
import io.xpipe.app.util.Validator;
import io.xpipe.core.source.DataSource;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import lombok.Getter;
import net.synedra.validatorfx.Check;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class NamedSourceChoiceComp extends SimpleComp implements Validatable {
private final ObservableValue<Predicate<DataSource<?>>> filter;
private final DataSourceProvider.Category category;
private final Property<? extends DataSource<?>> selected;
private final StringProperty filterString = new SimpleStringProperty();
@Getter
private final Validator validator = new SimpleValidator();
private final Check check;
public NamedSourceChoiceComp(
ObservableValue<Predicate<DataSource<?>>> filter,
Property<? extends DataSource<?>> selected,
DataSourceProvider.Category category) {
this.filter = filter;
this.selected = selected;
this.category = category;
check = Validator.nonNull(validator, AppI18n.observable("source"), selected);
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> void setUpListener(ObservableValue<T> prop) {
prop.addListener((c, o, n) -> {
((Property<T>) selected).setValue(n);
});
}
private <T extends DataSource<?>> void refreshShown(ObservableList<T> list, ObservableList<T> shown) {
var filtered = list.filtered(source -> {
if (!filter.getValue().test(source)) {
return false;
}
var e = DataStorage.get().getSourceEntry(source).orElseThrow();
return filterString.get() == null
|| e.getName().toLowerCase().contains(filterString.get().toLowerCase());
});
shown.removeIf(store -> !filtered.contains(store));
filtered.forEach(store -> {
if (!shown.contains(store)) {
shown.add(store);
}
});
}
@SuppressWarnings("unchecked")
private <T extends DataSource<?>> Region create() {
var list = FXCollections.observableList(DataStorage.get().getSourceCollections().stream()
.map(dataSourceCollection -> dataSourceCollection.getEntries())
.flatMap(Collection::stream)
.filter(entry -> entry.getState().isUsable())
.map(DataSourceEntry::getSource)
.map(source -> (T) source)
.toList());
var shown = FXCollections.<T>observableArrayList();
refreshShown(list, shown);
filter.addListener((observable, oldValue, newValue) -> {
refreshShown(list, shown);
});
filterString.addListener((observable, oldValue, newValue) -> {
refreshShown(list, shown);
});
var prop = new SimpleObjectProperty<T>();
setUpListener(prop);
var filterComp = new FilterComp(filterString).hide(Bindings.greaterThan(5, Bindings.size(shown)));
var view = new ListViewComp<>(shown, list, prop, (T s) -> {
var e = DataStorage.get().getSourceEntry(s).orElseThrow();
var provider = e.getProvider();
var graphic = provider.getDisplayIconFileName();
var top = String.format("%s (%s)", e.getName(), provider.getDisplayName());
var bottom = DataStoreProviders.byStore(e.getStore()).toSummaryString(e.getStore(), 100);
var el = JfxHelper.createNamedEntry(top, bottom, graphic);
VBox.setVgrow(el, Priority.ALWAYS);
return Comp.of(() -> el);
})
.apply(struc -> {
struc.get().setMaxHeight(3500);
check.decorates(struc.get());
});
var box = new VerticalComp(List.of(filterComp, view));
var text = new LabelComp(AppI18n.observable("noMatchingSourceFound"))
.apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS));
var notice = new VerticalComp(List.of(text))
.apply(struc -> {
struc.get().setSpacing(10);
struc.get().setAlignment(Pos.CENTER);
})
.hide(BindingsHelper.persist(Bindings.notEqual(0, Bindings.size(shown))));
return new StackComp(List.of(box, notice))
.styleClass("named-source-choice")
.createRegion();
}
@Override
protected Region createSimple() {
return create();
}
}

View file

@ -1,197 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import com.jfoenix.controls.JFXTextField;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.TrackEvent;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.effect.Glow;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.io.File;
public class SourceCollectionComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceCollectionComp(SourceCollectionWrapper group) {
this.group = group;
}
@Override
protected Region createSimple() {
var r = createContent();
var sp = new StackPane(r);
sp.setAlignment(Pos.CENTER);
sp.setOnMouseClicked(e -> {
if (e.getButton() != MouseButton.PRIMARY) {
return;
}
TrackEvent.withDebug("Storage group clicked")
.tag("uuid", group.getCollection().getUuid().toString())
.tag("name", group.getName())
.build()
.handle();
// StorageViewState.get().selectedGroupProperty().set(group);
e.consume();
});
setupDragAndDrop(sp);
return sp;
}
private void setupDragAndDrop(Region r) {
r.setOnDragOver(event -> {
// Moving storage entries
if (event.getGestureSource() != null
&& event.getGestureSource() != r
&& event.getSource() instanceof Node) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
r.setEffect(new Glow(0.5));
}
// Files from the outside
else if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.COPY);
r.setEffect(new Glow(0.5));
}
event.consume();
});
r.setOnDragExited(event -> {
r.setEffect(null);
event.consume();
});
r.setOnDragDropped(event -> {
// Moving storage entries
if (event.getGestureSource() != null
&& event.getGestureSource() != r
&& event.getGestureSource() instanceof Node n) {
var entry = n.getProperties().get("entry");
if (entry != null) {
var cast = (SourceEntryWrapper) entry;
cast.moveTo(this.group);
}
}
// Files from the outside
else if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
event.setDropCompleted(true);
Dragboard db = event.getDragboard();
db.getFiles().stream().map(File::toPath).forEach(group::dropFile);
}
event.consume();
});
}
private Label createDate() {
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(group.lastAccessProperty())));
date.getStyleClass().add("date");
return date;
}
private Region createContent() {
Region nameR;
if (!group.isRenameable()) {
Region textFieldR = new JFXTextField(group.getName());
textFieldR.setDisable(true);
var tempNote = Comp.of(() -> {
var infoIcon = new FontIcon("mdi2i-information-outline");
infoIcon.setOpacity(0.75);
return new StackPane(infoIcon);
})
.apply(new FancyTooltipAugment<>(AppI18n.observable("temporaryCollectionNote")))
.createRegion();
var label = new Label(group.getName(), tempNote);
label.getStyleClass().add("temp");
label.setAlignment(Pos.CENTER);
label.setContentDisplay(ContentDisplay.RIGHT);
nameR = new HBox(label);
} else {
var text = new LazyTextFieldComp(group.nameProperty());
nameR = text.createRegion();
}
var options = new IconButtonComp("mdomz-settings");
var cm = new SourceCollectionContextMenu<>(true, group, nameR);
options.apply(new SourceCollectionContextMenu<>(true, group, nameR))
.apply(r -> {
AppFont.setSize(r.get(), -1);
r.get().setPadding(new Insets(3, 5, 3, 5));
})
.apply(new FancyTooltipAugment<>("collectionOptions"));
var count = new CountComp<>(
SourceCollectionViewState.get().getFilteredEntries(this.group), this.group.entriesProperty());
var spacer = new Region();
var optionsR = options.createRegion();
var top = new HBox(nameR, optionsR);
HBox.setHgrow(nameR, Priority.ALWAYS);
top.setSpacing(8);
var countR = count.createRegion();
countR.prefWidthProperty().bind(optionsR.widthProperty());
var bottom = new HBox(createDate(), spacer, countR);
bottom.setAlignment(Pos.CENTER);
bottom.setSpacing(8);
HBox.setHgrow(spacer, Priority.ALWAYS);
var right = new VBox(top, bottom);
right.setSpacing(8);
AppFont.header(top);
AppFont.small(bottom);
var svgContent = Bindings.createObjectBinding(
() -> {
if (SourceCollectionViewState.get().getSelectedGroup() == group) {
return "folder_open.svg";
} else {
return "folder_closed.svg";
}
},
SourceCollectionViewState.get().selectedGroupProperty());
var svg = new PrettyImageComp(svgContent, 55, 55).createRegion();
svg.getStyleClass().add("icon");
if (group.isInternal()) {
svg.setOpacity(0.3);
}
var hbox = new HBox(svg, right);
HBox.setHgrow(right, Priority.ALWAYS);
hbox.setAlignment(Pos.CENTER);
// svg.prefHeightProperty().bind(right.heightProperty());
// svg.prefWidthProperty().bind(right.heightProperty());
hbox.setSpacing(5);
hbox.getStyleClass().add("storage-group-entry");
cm = new SourceCollectionContextMenu<>(false, group, nameR);
cm.augment(new SimpleCompStructure<>(hbox));
hbox.setMinWidth(0);
return hbox;
}
}

View file

@ -1,105 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.DesktopHelper;
import javafx.scene.control.Alert;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceCollectionContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
public SourceCollectionContextMenu(
boolean showOnPrimaryButton, SourceCollectionWrapper group, Region renameTextField) {
super(() -> createContextMenu(group, renameTextField));
}
private static void onDelete(SourceCollectionWrapper group) {
if (group.getEntries().size() > 0) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
alert.setHeaderText(AppI18n.get("confirmCollectionDeletionHeader", group.getName()));
alert.setContentText(AppI18n.get(
"confirmCollectionDeletionContent",
group.getEntries().size()));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
group.delete();
});
} else {
group.delete();
}
}
private static void onClean(SourceCollectionWrapper group) {
if (group.getEntries().size() > 0) {
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("confirmCollectionDeletionTitle"));
alert.setHeaderText(AppI18n.get("confirmCollectionDeletionHeader", group.getName()));
alert.setContentText(AppI18n.get(
"confirmCollectionDeletionContent",
group.getEntries().size()));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
group.clean();
});
} else {
group.clean();
}
}
protected static ContextMenu createContextMenu(SourceCollectionWrapper group, Region renameTextField) {
var cm = new ContextMenu();
var name = new MenuItem(group.getName());
name.setDisable(true);
name.getStyleClass().add("header-menu-item");
cm.getItems().add(name);
cm.getItems().add(new SeparatorMenuItem());
{
var properties = new MenuItem(AppI18n.get("properties"), new FontIcon("mdi2a-application-cog"));
properties.setOnAction(e -> {});
// cm.getItems().add(properties);
}
if (group.isRenameable()) {
var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdal-edit"));
rename.setOnAction(e -> {
renameTextField.requestFocus();
});
cm.getItems().add(rename);
}
if (AppPrefs.get().developerMode().getValue()) {
var openDir = new MenuItem(AppI18n.get("openDir"), new FontIcon("mdal-edit"));
openDir.setOnAction(e -> {
DesktopHelper.browseFileInDirectory(group.getCollection().getDirectory());
});
cm.getItems().add(openDir);
}
if (group.isDeleteable()) {
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
onDelete(group);
});
cm.getItems().add(del);
} else {
var del = new MenuItem(AppI18n.get("clean"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
onClean(group);
});
cm.getItems().add(del);
}
return cm;
}
}

View file

@ -1,81 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.core.source.DataSourceType;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceCollectionEmptyIntroComp extends SimpleComp {
@Override
protected Region createSimple() {
var title = new Label(AppI18n.get("dataSourceIntroTitle"));
AppFont.setSize(title, 7);
title.getStyleClass().add("title-header");
var descFi = new FontIcon("mdi2i-information-outline");
var introDesc = new Label(AppI18n.get("dataSourceIntroDescription"));
introDesc.heightProperty().addListener((c, o, n) -> {
descFi.iconSizeProperty().set(n.intValue());
});
var tableFi = new DataSourceTypeComp(DataSourceType.TABLE, null).createRegion();
var table = new Label(AppI18n.get("dataSourceIntroTable"), tableFi);
tableFi.prefWidthProperty().bind(table.heightProperty());
tableFi.prefHeightProperty().bind(table.heightProperty());
var structureFi = new DataSourceTypeComp(DataSourceType.STRUCTURE, null).createRegion();
var structure = new Label(AppI18n.get("dataSourceIntroStructure"), structureFi);
structureFi.prefWidthProperty().bind(structure.heightProperty());
structureFi.prefHeightProperty().bind(structure.heightProperty());
var textFi = new DataSourceTypeComp(DataSourceType.TEXT, null).createRegion();
var text = new Label(AppI18n.get("dataSourceIntroText"), textFi);
textFi.prefWidthProperty().bind(text.heightProperty());
textFi.prefHeightProperty().bind(text.heightProperty());
var binaryFi = new DataSourceTypeComp(DataSourceType.RAW, null).createRegion();
var binary = new Label(AppI18n.get("dataSourceIntroBinary"), binaryFi);
binaryFi.prefWidthProperty().bind(binary.heightProperty());
binaryFi.prefHeightProperty().bind(binary.heightProperty());
var collectionFi = new DataSourceTypeComp(DataSourceType.COLLECTION, null).createRegion();
var collection = new Label(AppI18n.get("dataSourceIntroCollection"), collectionFi);
collectionFi.prefWidthProperty().bind(collection.heightProperty());
collectionFi.prefHeightProperty().bind(collection.heightProperty());
var v = new VBox(
title,
introDesc,
new Separator(Orientation.HORIZONTAL),
table,
new Separator(Orientation.HORIZONTAL),
structure,
new Separator(Orientation.HORIZONTAL),
text,
new Separator(Orientation.HORIZONTAL),
binary,
new Separator(Orientation.HORIZONTAL),
collection);
v.setMinWidth(Region.USE_PREF_SIZE);
v.setMaxWidth(Region.USE_PREF_SIZE);
v.setMinHeight(Region.USE_PREF_SIZE);
v.setMaxHeight(Region.USE_PREF_SIZE);
v.setSpacing(10);
v.getStyleClass().add("intro");
var sp = new StackPane(v);
sp.setAlignment(Pos.CENTER);
return sp;
}
}

View file

@ -1,60 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
public class SourceCollectionFilterBarComp extends SimpleComp {
private Region createGroupListHeader() {
var label = new Label("Collections");
label.getStyleClass().add("name");
var count = new CountComp<>(
SourceCollectionViewState.get().getShownGroups(),
SourceCollectionViewState.get().getAllGroups());
var newFolder = new IconButtonComp("mdi2f-folder-plus-outline", () -> {
SourceCollectionViewState.get().addNewCollection();
})
.shortcut(new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addCollectionFolder"));
var spacer = new Region();
var topBar = new HBox(label, count.createRegion(), spacer, newFolder.createRegion());
AppFont.header(topBar);
topBar.setAlignment(Pos.CENTER);
HBox.setHgrow(spacer, Priority.ALWAYS);
topBar.getStyleClass().add("top");
return topBar;
}
private Region createGroupListFilter() {
var filter = new FilterComp(SourceCollectionViewState.get().getFilter().filterProperty());
filter.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
s.getText().requestFocus();
});
var r = new StackPane(filter.createRegion());
r.setAlignment(Pos.CENTER);
r.getStyleClass().add("filter-bar");
AppFont.medium(r);
return r;
}
@Override
public Region createSimple() {
var content = new VBox(createGroupListHeader(), createGroupListFilter());
content.getStyleClass().add("bar");
content.getStyleClass().add("collections-bar");
return content;
}
}

View file

@ -1,106 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.storage.source.SourceEntryListComp;
import io.xpipe.app.comp.storage.source.SourceEntryListHeaderComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.List;
public class SourceCollectionLayoutComp extends SimpleComp {
private Comp<?> createEntries(SourceCollectionWrapper group, Region groupHeader) {
var entryList = new SourceEntryListComp(group);
var entriesHeader = new SourceEntryListHeaderComp(group);
entriesHeader.apply(r -> r.get().minHeightProperty().bind(groupHeader.heightProperty()));
var entriesHeaderWrapped = Comp.derive(entriesHeader, r -> {
var sp = new StackPane(r);
sp.setPadding(new Insets(0, 5, 5, 5));
return sp;
});
var list = new ArrayList<Comp<?>>(List.of(entriesHeaderWrapped));
list.add(entryList);
entryList.apply(s -> VBox.setVgrow(s.get(), Priority.ALWAYS));
return new VerticalComp(list);
}
private Comp<?> createCollectionList() {
var listComp = new SourceCollectionListComp();
listComp.apply(s -> s.get().setPrefHeight(Region.USE_COMPUTED_SIZE));
return listComp;
}
private Comp<?> createFiller() {
var filler = Comp.of(() -> new Region());
filler.styleClass("bar");
filler.styleClass("filler-bar");
var button = new ButtonComp(
AppI18n.observable("addCollection"), new FontIcon("mdi2f-folder-plus-outline"), () -> {
SourceCollectionViewState.get().addNewCollection();
})
.apply(new FancyTooltipAugment<>("addCollectionFolder"));
button.styleClass("intro-add-collection-button");
var pane = Comp.derive(button, r -> {
var sp = new StackPane(r);
sp.setAlignment(Pos.CENTER);
sp.setPickOnBounds(false);
return sp;
});
pane.apply(r -> {
r.get().visibleProperty().bind(SourceCollectionViewState.get().getStorageEmpty());
r.get()
.mouseTransparentProperty()
.bind(BindingsHelper.persist(
Bindings.not(SourceCollectionViewState.get().getStorageEmpty())));
});
var stack = new StackComp(List.of(filler, pane));
stack.apply(s -> {
s.get().setMinHeight(0);
s.get().setPrefHeight(0);
});
return stack;
}
@Override
protected Region createSimple() {
var listComp = createCollectionList();
var r = new BorderPane();
var listR = listComp.createRegion();
var groupHeader = new SourceCollectionFilterBarComp().createRegion();
var filler = createFiller().createRegion();
var groups = new VBox(groupHeader, listR);
groups.getStyleClass().add("sidebar");
VBox.setVgrow(filler, Priority.SOMETIMES);
VBox.setVgrow(listR, Priority.SOMETIMES);
r.setLeft(groups);
Runnable update = () -> {
r.setCenter(createEntries(SourceCollectionViewState.get().getSelectedGroup(), groupHeader)
.createRegion());
};
update.run();
SourceCollectionViewState.get().selectedGroupProperty().addListener((c, o, n) -> {
PlatformThread.runLaterIfNeeded(update);
});
return r;
}
}

View file

@ -1,17 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.base.ListViewComp;
public class SourceCollectionListComp extends ListViewComp<SourceCollectionWrapper> {
public SourceCollectionListComp() {
super(
SourceCollectionViewState.get().getShownGroups(),
SourceCollectionViewState.get().getAllGroups(),
SourceCollectionViewState.get().selectedGroupProperty(),
SourceCollectionComp::new);
styleClass("storage-group-list-comp");
styleClass("bar");
apply(s -> s.get().layout());
}
}

View file

@ -1,66 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import java.time.Instant;
import java.util.Comparator;
public interface SourceCollectionSortMode {
SourceCollectionSortMode ALPHABETICAL_DESC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "alphabetical-desc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, String>comparing(
e -> e.getName().getValue())
.reversed();
}
};
SourceCollectionSortMode ALPHABETICAL_ASC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "alphabetical-asc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, String>comparing(
e -> e.getName().getValue());
}
};
SourceCollectionSortMode DATE_DESC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "date-desc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.<SourceEntryWrapper, Instant>comparing(
e -> e.getLastUsed().getValue())
.reversed();
}
};
SourceCollectionSortMode DATE_ASC = new SourceCollectionSortMode() {
@Override
public String getId() {
return "date-asc";
}
@Override
public Comparator<SourceEntryWrapper> comparator() {
return Comparator.comparing(e -> e.getLastUsed().getValue());
}
};
String getId();
Comparator<SourceEntryWrapper> comparator();
}

View file

@ -1,222 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.time.Instant;
import java.util.Comparator;
import java.util.concurrent.CopyOnWriteArrayList;
public class SourceCollectionViewState {
private static SourceCollectionViewState INSTANCE;
private final StorageFilter filter = new StorageFilter();
private final ObservableList<SourceCollectionWrapper> allGroups =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableList<SourceCollectionWrapper> shownGroups =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final SimpleObjectProperty<SourceCollectionWrapper> selectedGroup = new SimpleObjectProperty<>();
private final SimpleObjectProperty<SourceCollectionSortMode> sortMode = new SimpleObjectProperty<>();
private final ObservableList<SourceEntryWrapper> allEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableList<SourceEntryWrapper> shownEntries =
FXCollections.observableList(new CopyOnWriteArrayList<>());
private final ObservableBooleanValue storageEmpty =
BindingsHelper.persist(Bindings.size(allGroups).isEqualTo(0));
private SourceCollectionViewState() {
addCollectionListChangeListeners();
addEntryListListeners();
addSortModeListeners();
}
public static void init() {
INSTANCE = new SourceCollectionViewState();
}
public static void reset() {
INSTANCE = null;
}
public static SourceCollectionViewState get() {
return INSTANCE;
}
private void addSortModeListeners() {
ChangeListener<SourceCollectionSortMode> listener = (observable1, oldValue1, newValue1) -> {
sortMode.set(newValue1);
};
selectedGroup.addListener((observable, oldValue, newValue) -> {
sortMode.set(newValue != null ? newValue.getSortMode() : null);
if (newValue != null) {
newValue.sortModeProperty().addListener(listener);
}
if (oldValue != null) {
oldValue.sortModeProperty().removeListener(listener);
}
});
}
public void addNewCollection() {
PlatformThread.runLaterIfNeeded(() -> {
var col = DataSourceCollection.createNew(AppI18n.get("newCollection"));
DataStorage.get().addCollection(col);
allGroups.stream()
.filter(g -> g.getCollection().equals(col))
.findAny()
.ifPresent(selectedGroup::set);
});
}
public ObservableList<SourceEntryWrapper> getAllEntries() {
return allEntries;
}
public ObservableList<SourceEntryWrapper> getShownEntries() {
return shownEntries;
}
public ObservableBooleanValue getStorageEmpty() {
return storageEmpty;
}
public SourceCollectionWrapper getSelectedGroup() {
return selectedGroup.get();
}
public SimpleObjectProperty<SourceCollectionWrapper> selectedGroupProperty() {
return selectedGroup;
}
private void addCollectionListChangeListeners() {
allGroups.setAll(filter(FXCollections.observableList(DataStorage.get().getSourceCollections().stream()
.map(SourceCollectionWrapper::new)
.toList())));
filter.createFilterBinding(
filter(allGroups),
shownGroups,
new SimpleObjectProperty<>(
Comparator.<SourceCollectionWrapper, Instant>comparing(e -> e.getLastAccess())
.reversed()));
DataStorage.get().addListener(new StorageListener() {
@Override
public void onStoreAdd(DataStoreEntry entry) {}
@Override
public void onStoreRemove(DataStoreEntry entry) {}
@Override
public void onCollectionAdd(DataSourceCollection collection) {
PlatformThread.runLaterIfNeeded(() -> {
var sg = new SourceCollectionWrapper(collection);
allGroups.add(sg);
});
}
@Override
public void onCollectionRemove(DataSourceCollection collection) {
PlatformThread.runLaterIfNeeded(() -> {
allGroups.removeIf(g -> g.getCollection().equals(collection));
});
}
});
shownGroups.addListener((ListChangeListener<? super SourceCollectionWrapper>) (c) -> {
if (selectedGroup.get() != null && !shownGroups.contains(selectedGroup.get())) {
selectedGroup.set(null);
}
});
shownGroups.addListener((ListChangeListener<? super SourceCollectionWrapper>) c -> {
if (c.getList().size() == 1) {
selectedGroup.set(c.getList().get(0));
}
});
}
private ObservableList<SourceCollectionWrapper> filter(ObservableList<SourceCollectionWrapper> list) {
return list.filtered(storeEntryWrapper -> {
if (AppPrefs.get().developerMode().getValue()
&& AppPrefs.get().developerShowHiddenEntries().get()) {
return true;
} else {
return !storeEntryWrapper.isInternal();
}
});
}
public SourceCollectionWrapper getGroup(SourceEntryWrapper e) {
return allGroups.stream()
.filter(g -> g.getEntries().contains(e))
.findFirst()
.orElseThrow();
}
public ObservableList<SourceEntryWrapper> getFilteredEntries(SourceCollectionWrapper g) {
var filtered = FXCollections.<SourceEntryWrapper>observableArrayList();
filter.createFilterBinding(
g.entriesProperty(),
filtered,
new SimpleObjectProperty<>(Comparator.<SourceEntryWrapper, Instant>comparing(
e -> e.getEntry().getLastAccess())
.reversed()));
return filtered;
}
private void addEntryListListeners() {
filter.createFilterBinding(
allEntries,
shownEntries,
Bindings.createObjectBinding(
() -> {
return sortMode.getValue() != null
? sortMode.getValue().comparator()
: Comparator.<SourceEntryWrapper>comparingInt(o -> o.hashCode());
},
sortMode));
selectedGroup.addListener((c, o, n) -> {
if (o != null) {
Bindings.unbindContent(allEntries, o.getEntries());
}
if (n != null) {
Bindings.bindContent(allEntries, n.getEntries());
}
});
}
public ObservableList<SourceCollectionWrapper> getShownGroups() {
return shownGroups;
}
public ObservableList<SourceCollectionWrapper> getAllGroups() {
return allGroups;
}
public StorageFilter getFilter() {
return filter;
}
}

View file

@ -1,164 +0,0 @@
package io.xpipe.app.comp.storage.collection;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.source.SourceEntryDisplayMode;
import io.xpipe.app.comp.storage.source.SourceEntryWrapper;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.storage.CollectionListener;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.impl.FileStore;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SourceCollectionWrapper implements StorageFilter.Filterable {
private final Property<String> name;
private final IntegerProperty size;
private final ListProperty<SourceEntryWrapper> entries;
private final DataSourceCollection collection;
private final Property<Instant> lastAccess;
private final Property<SourceCollectionSortMode> sortMode =
new SimpleObjectProperty<>(SourceCollectionSortMode.DATE_DESC);
private final Property<SourceEntryDisplayMode> displayMode =
new SimpleObjectProperty<>(SourceEntryDisplayMode.LIST);
public SourceCollectionWrapper(DataSourceCollection collection) {
this.collection = collection;
this.entries = new SimpleListProperty<>(FXCollections.observableList(collection.getEntries().stream()
.map(SourceEntryWrapper::new)
.collect(Collectors.toCollection(ArrayList::new))));
this.size = new SimpleIntegerProperty(collection.getEntries().size());
this.name = new SimpleStringProperty(collection.getName());
this.lastAccess = new SimpleObjectProperty<>(collection.getLastAccess().minus(Duration.ofMillis(500)));
setupListeners();
}
public ReadOnlyBooleanProperty emptyProperty() {
return entries.emptyProperty();
}
public boolean isDeleteable() {
return !isInternal();
}
public boolean isRenameable() {
return !isInternal();
}
public boolean isInternal() {
return collection.equals(DataStorage.get().getInternalCollection());
}
public void dropFile(Path file) {
var store = FileStore.local(file);
GuiDsCreatorMultiStep.showForStore(DataSourceProvider.Category.STREAM, store, this.getCollection());
}
public void delete() {
DataStorage.get().deleteCollection(this.collection);
}
public void clean() {
var entries = List.copyOf(collection.getEntries());
entries.forEach(e -> DataStorage.get().deleteSourceEntry(e));
}
private void setupListeners() {
name.addListener((c, o, n) -> {
collection.setName(n);
});
collection.addListener(new CollectionListener() {
@Override
public void onUpdate() {
lastAccess.setValue(collection.getLastAccess().minus(Duration.ofMillis(500)));
name.setValue(collection.getName());
}
@Override
public void onEntryAdd(DataSourceEntry entry) {
var e = new SourceEntryWrapper(entry);
entries.add(e);
}
@Override
public void onEntryRemove(DataSourceEntry entry) {
entries.removeIf(e -> e.getEntry().equals(entry));
}
});
}
public DataSourceCollection getCollection() {
return collection;
}
public String getName() {
return name.getValue();
}
public Property<String> nameProperty() {
return name;
}
public int getSize() {
return size.get();
}
public IntegerProperty sizeProperty() {
return size;
}
public ObservableList<SourceEntryWrapper> getEntries() {
return entries.get();
}
public ListProperty<SourceEntryWrapper> entriesProperty() {
return entries;
}
@Override
public boolean shouldShow(String filter) {
if (isInternal()) {
// return getEntries().stream().anyMatch(e -> e.shouldShow(filter));
}
return getName().toLowerCase().contains(filter.toLowerCase())
|| entries.stream().anyMatch(e -> e.shouldShow(filter));
}
public Instant getLastAccess() {
return lastAccess.getValue();
}
public Property<Instant> lastAccessProperty() {
return lastAccess;
}
public SourceCollectionSortMode getSortMode() {
return sortMode.getValue();
}
public Property<SourceCollectionSortMode> sortModeProperty() {
return sortMode;
}
public SourceEntryDisplayMode getDisplayMode() {
return displayMode.getValue();
}
public Property<SourceEntryDisplayMode> displayModeProperty() {
return displayMode;
}
}

View file

@ -1,224 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.comp.source.DsDataTransferComp;
import io.xpipe.app.comp.storage.DataSourceTypeComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppResources;
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.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.core.store.DataFlow;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
import lombok.SneakyThrows;
public class SourceEntryComp extends SimpleComp {
private static final double SOURCE_TYPE_WIDTH = 0.09;
private static final double NAME_WIDTH = 0.3;
private static final double DETAILS_WIDTH = 0.43;
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
private static Image DND_IMAGE = null;
private final SourceEntryWrapper entry;
public SourceEntryComp(SourceEntryWrapper entry) {
this.entry = entry;
}
private Label createSize() {
var size = new Label();
size.textProperty().bind(PlatformThread.sync(entry.getStoreSummary()));
size.getStyleClass().add("size");
AppFont.small(size);
return size;
}
private LazyTextFieldComp createName() {
var name = new LazyTextFieldComp(entry.getName());
name.apply(s -> AppFont.header(s.get()));
return name;
}
private void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
switch (val) {
case LOAD_FAILED -> {
node.pseudoClassStateChanged(FAILED, true);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
case INCOMPLETE -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, true);
}
default -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
}
});
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
}
protected Region createContent() {
var name = createName().createRegion();
var size = createSize();
var img = entry.getState().getValue() == DataSourceEntry.State.LOAD_FAILED
? "disabled_icon.png"
: entry.getEntry().getProvider().getDisplayIconFileName();
var storeIcon = new PrettyImageComp(new SimpleStringProperty(img), 60, 50).createRegion();
var desc = new LabelComp(entry.getInformation()).createRegion();
desc.getStyleClass().add("description");
AppFont.header(desc);
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.getLastUsed())));
date.getStyleClass().add("date");
AppFont.small(date);
var grid = new GridPane();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, SOURCE_TYPE_WIDTH),
createShareConstraint(grid, NAME_WIDTH),
new ColumnConstraints(-1));
var typeLogo = new DataSourceTypeComp(
entry.getEntry().getDataSourceType(),
entry.getDataFlow().getValue())
.createRegion();
typeLogo.maxWidthProperty().bind(typeLogo.heightProperty());
grid.add(typeLogo, 0, 0, 1, 2);
GridPane.setHalignment(typeLogo, HPos.CENTER);
grid.add(name, 1, 0);
grid.add(date, 1, 1);
grid.add(storeIcon, 2, 0, 1, 2);
grid.add(size, 3, 1);
grid.add(desc, 3, 0);
grid.setVgap(5);
AppFont.small(size);
AppFont.small(date);
grid.prefHeightProperty()
.bind(Bindings.createDoubleBinding(
() -> {
return name.getHeight() + date.getHeight() + 5;
},
name.heightProperty(),
date.heightProperty()));
grid.getStyleClass().add("content");
grid.setMaxHeight(100);
grid.setHgap(8);
var buttons = new HBox();
buttons.setFillHeight(true);
buttons.getChildren().add(createPipeButton().createRegion());
// buttons.getChildren().add(createUpdateButton().createRegion());
buttons.getChildren().add(createSettingsButton(name).createRegion());
buttons.setMinWidth(Region.USE_PREF_SIZE);
var hbox = new HBox(grid, buttons);
hbox.getStyleClass().add("storage-entry-comp");
HBox.setHgrow(grid, Priority.ALWAYS);
buttons.prefHeightProperty().bind(hbox.heightProperty());
hbox.getProperties().put("entry", this.entry);
hbox.setOnDragDetected(e -> {
if (!entry.getUsable().get()) {
return;
}
if (DND_IMAGE == null) {
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "img/file_drag_icon.png")
.orElseThrow();
DND_IMAGE = new Image(url.toString(), 80, 80, true, false);
}
Dragboard db = hbox.startDragAndDrop(TransferMode.MOVE);
var cc = new ClipboardContent();
cc.putString("");
db.setContent(cc);
db.setDragView(DND_IMAGE, 30, 60);
e.consume();
});
applyState(hbox);
return hbox;
}
private Comp<?> createSettingsButton(Region nameField) {
var settingsButton = new IconButtonComp("mdi2v-view-headline");
settingsButton.styleClass("settings");
settingsButton.apply(new SourceEntryContextMenu<>(true, entry, nameField));
settingsButton.apply(GrowAugment.create(false, true));
settingsButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
settingsButton.apply(new FancyTooltipAugment<>("entrySettings"));
return settingsButton;
}
private Comp<?> createPipeButton() {
var pipeButton = new IconButtonComp("mdi2p-pipe-disconnected", () -> {
DsDataTransferComp.showPipeWindow(this.entry.getEntry());
});
pipeButton.styleClass("retrieve");
pipeButton.apply(GrowAugment.create(false, true));
pipeButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
var disabled = Bindings.createBooleanBinding(
() -> {
if (entry.getDataFlow().getValue() == null) {
return true;
}
return entry.getDataFlow().getValue() == DataFlow.OUTPUT
|| entry.getDataFlow().getValue() == DataFlow.TRANSFORMER;
},
entry.getDataFlow());
pipeButton.disable(disabled).apply(s -> s.get());
pipeButton.apply(new FancyTooltipAugment<>("retrieve"));
return pipeButton;
}
private ColumnConstraints createShareConstraint(Region r, double share) {
var cc = new ColumnConstraints();
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
cc.setMaxWidth(750 * share);
return cc;
}
}

View file

@ -1,91 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.util.DesktopHelper;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceEntryContextMenu<S extends CompStructure<?>> extends ContextMenuAugment<S> {
public SourceEntryContextMenu(boolean showOnPrimaryButton, SourceEntryWrapper entry, Region renameTextField) {
super(() -> createContextMenu(entry, renameTextField));
}
protected static ContextMenu createContextMenu(SourceEntryWrapper entry, Region renameTextField) {
var cm = new ContextMenu();
AppFont.normal(cm.getStyleableNode());
for (var actionProvider : entry.getActionProviders()) {
var c = actionProvider.getDataSourceCallSite();
var name = c.getName(entry.getEntry().getSource().asNeeded());
var icon = c.getIcon(entry.getEntry().getSource().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
event.consume();
try {
var action = c.createAction(entry.getEntry().getSource().asNeeded());
action.execute();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
item.textProperty().bind(name);
// item.setDisable(!entry.getState().getValue().isUsable());
cm.getItems().add(item);
// actionProvider.applyToRegion(entry.getEntry().getStore().asNeeded(), region);
}
if (entry.getActionProviders().size() > 0) {
cm.getItems().add(new SeparatorMenuItem());
}
var properties = new MenuItem(AppI18n.get("properties"), new FontIcon("mdi2a-application-cog"));
properties.setOnAction(e -> {});
// cm.getItems().add(properties);
var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdi2r-rename-box"));
rename.setOnAction(e -> {
renameTextField.requestFocus();
});
cm.getItems().add(rename);
var validate = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
validate.setOnAction(event -> {
});
cm.getItems().add(validate);
var edit = new MenuItem(AppI18n.get("edit"), new FontIcon("mdal-edit"));
edit.setOnAction(event -> entry.editDialog());
edit.disableProperty().bind(Bindings.equal(DataSourceEntry.State.LOAD_FAILED, entry.getState()));
cm.getItems().add(edit);
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.setOnAction(e -> {
entry.delete();
});
cm.getItems().add(del);
if (AppPrefs.get().developerMode().getValue()) {
cm.getItems().add(new SeparatorMenuItem());
var openDir = new MenuItem(AppI18n.get("browseInternal"), new FontIcon("mdi2f-folder-open-outline"));
openDir.setOnAction(e -> {
DesktopHelper.browsePath(entry.getEntry().getDirectory());
});
cm.getItems().add(openDir);
}
return cm;
}
}

View file

@ -1,44 +0,0 @@
package io.xpipe.app.comp.storage.source;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import java.util.List;
import java.util.stream.Collectors;
public interface SourceEntryDisplayMode {
SourceEntryDisplayMode LIST = new ListMode();
SourceEntryDisplayMode TILES = new ListMode();
Region create(List<SourceEntryWrapper> entries);
class ListMode implements SourceEntryDisplayMode {
private static final double SOURCE_TYPE_WIDTH = 0.15;
private static final double NAME_WIDTH = 0.4;
private static final double STORE_TYPE_WIDTH = 0.1;
private static final double DETAILS_WIDTH = 0.35;
@Override
public Region create(List<SourceEntryWrapper> entries) {
VBox content = new VBox();
Runnable updateList = () -> {
var nw = entries.stream()
.map(v -> {
return new SourceEntryComp(v).createRegion();
})
.collect(Collectors.toList());
content.getChildren().setAll(nw);
};
updateList.run();
content.setFillWidth(true);
content.setSpacing(5);
content.getStyleClass().add("content");
content.getStyleClass().add("list-mode");
return content;
}
}
}

View file

@ -1,70 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.FileDropOverlayComp;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.storage.collection.SourceCollectionEmptyIntroComp;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import javafx.application.Platform;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.ListChangeListener;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import java.util.List;
import java.util.Map;
public class SourceEntryListComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceEntryListComp(SourceCollectionWrapper group) {
this.group = group;
}
@SuppressWarnings("unchecked")
private Region createList() {
if (group == null) {
return null;
}
var content =
group.getDisplayMode().create(SourceCollectionViewState.get().getShownEntries());
var cp = new ScrollPane(content);
cp.setFitToWidth(true);
content.getStyleClass().add("content-pane");
cp.getStyleClass().add("storage-entry-list-comp");
SourceCollectionViewState.get().getShownEntries().addListener((ListChangeListener<? super SourceEntryWrapper>)
(c) -> {
Platform.runLater(() -> {
cp.setContent(group.getDisplayMode().create((List<SourceEntryWrapper>) c.getList()));
});
});
return cp;
}
@Override
protected Region createSimple() {
Map<Comp<?>, ObservableBooleanValue> map;
if (group == null) {
map = Map.of(
new SourceStorageEmptyIntroComp(),
SourceCollectionViewState.get().getStorageEmpty());
} else {
map = Map.of(
Comp.of(() -> createList()),
group.emptyProperty().not(),
new SourceCollectionEmptyIntroComp(),
group.emptyProperty());
}
var overlay = new FileDropOverlayComp<>(new MultiContentComp(map), files -> {
files.forEach(group::dropFile);
});
return overlay.createRegion();
}
}

View file

@ -1,295 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.collection.SourceCollectionSortMode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
public class SourceEntryListHeaderComp extends SimpleComp {
private final SourceCollectionWrapper group;
public SourceEntryListHeaderComp(SourceCollectionWrapper group) {
this.group = group;
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
group.sortModeProperty());
var alphabetical = new IconButtonComp(icon, () -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_DESC);
} else if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_ASC);
} else {
group.sortModeProperty().setValue(SourceCollectionSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_ASC
|| group.getSortMode() == SourceCollectionSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
group.sortModeProperty()));
});
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
group.sortModeProperty());
var date = new IconButtonComp(icon, () -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_DESC);
} else if (group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_ASC);
} else {
group.sortModeProperty().setValue(SourceCollectionSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getSortMode() == SourceCollectionSortMode.DATE_ASC
|| group.getSortMode() == SourceCollectionSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
group.sortModeProperty()));
});
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
private Comp<?> createListDisplayModeButton() {
var list = new IconButtonComp("mdi2f-format-list-bulleted-type", () -> {
group.displayModeProperty().setValue(SourceEntryDisplayMode.LIST);
});
list.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getDisplayMode() == SourceEntryDisplayMode.LIST) {
return 1.0;
}
return 0.4;
},
group.displayModeProperty()));
});
list.apply(new FancyTooltipAugment<>("displayList"));
return list;
}
private Comp<?> createTilesDisplayModeButton() {
var tiles = new IconButtonComp("mdal-apps", () -> {
group.displayModeProperty().setValue(SourceEntryDisplayMode.TILES);
});
tiles.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (group.getDisplayMode() == SourceEntryDisplayMode.TILES) {
return 1.0;
}
return 0.4;
},
group.displayModeProperty()));
});
tiles.apply(new FancyTooltipAugment<>("displayTiles"));
return tiles;
}
private Comp<?> createSortButtonBar() {
return new HorizontalComp(List.of(createDateSortButton(), createAlphabeticalSortButton()));
}
private Comp<?> createDisplayModeButtonBar() {
return new HorizontalComp(List.of(createListDisplayModeButton(), createTilesDisplayModeButton()));
}
private Comp<?> createRightButtons() {
var v = new VerticalComp(List.of(
createDisplayModeButtonBar().apply(struc -> struc.get().setVisible(false)),
Comp.of(() -> {
return new StackPane(new Separator(Orientation.HORIZONTAL));
}),
createSortButtonBar()));
v.apply(r -> {
var sep = r.get().getChildren().get(1);
VBox.setVgrow(sep, Priority.ALWAYS);
})
.apply(s -> {
s.get()
.visibleProperty()
.bind(BindingsHelper.persist(Bindings.greaterThan(
Bindings.size(
SourceCollectionViewState.get().getAllEntries()),
0)));
});
return v;
}
@Override
protected Region createSimple() {
var label = new Label(AppI18n.get("none"));
if (SourceCollectionViewState.get().getSelectedGroup() != null) {
label.textProperty()
.bind(SourceCollectionViewState.get().getSelectedGroup().nameProperty());
}
label.getStyleClass().add("name");
SourceCollectionViewState.get().selectedGroupProperty().addListener((c, o, n) -> {
if (n != null) {
label.textProperty().bind(n.nameProperty());
}
});
var count = new CountComp<>(
SourceCollectionViewState.get().getShownEntries(),
SourceCollectionViewState.get().getAllEntries());
var close = new IconButtonComp("mdi2a-arrow-collapse-left", () -> SourceCollectionViewState.get()
.selectedGroupProperty()
.set(null))
.createRegion();
AppFont.medium(close);
var leftSep = new StackPane(new Separator(Orientation.HORIZONTAL));
var top = new HBox(label);
if (group != null) {
top.getChildren().add(0, close);
top.getChildren().addAll(count.createRegion());
}
top.setAlignment(Pos.CENTER_LEFT);
top.setSpacing(3);
var left = new VBox(top, leftSep, createActionsButtonBar().createRegion());
VBox.setVgrow(leftSep, Priority.ALWAYS);
var rspacer = new Region();
HBox.setHgrow(rspacer, Priority.ALWAYS);
var topBar = new HBox(left, rspacer);
if (group != null) {
var right = createRightButtons().createRegion();
topBar.getChildren().addAll(right);
}
topBar.setFillHeight(true);
topBar.setSpacing(13);
topBar.getStyleClass().add("top");
topBar.setAlignment(Pos.CENTER);
AppFont.header(topBar);
topBar.getStyleClass().add("bar");
topBar.getStyleClass().add("entry-bar");
return topBar;
}
private Comp<?> createActionsButtonBar() {
var newFile = new ButtonComp(
AppI18n.observable(group != null ? "addStream" : "pipeStream"),
new FontIcon("mdi2f-file-plus-outline"),
() -> {
var selected = SourceCollectionViewState.get()
.selectedGroupProperty()
.get();
GuiDsCreatorMultiStep.showCreation(
DataSourceProvider.Category.STREAM,
selected != null ? selected.getCollection() : null);
})
.shortcut(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addStreamDataSource"));
var newDb = new ButtonComp(
AppI18n.observable(group != null ? "addDatabase" : "pipeDatabase"),
new FontIcon("mdi2d-database-plus-outline"),
() -> {
var selected = SourceCollectionViewState.get()
.selectedGroupProperty()
.get();
GuiDsCreatorMultiStep.showCreation(
DataSourceProvider.Category.DATABASE,
selected != null ? selected.getCollection() : null);
})
.shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addDatabaseDataSource"));
// var newStructure = new IconButton("mdi2b-beaker-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.STRUCTURE);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addStructureDataSource")));
//
// var newText = new IconButton("mdi2t-text-box-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.TEXT);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addTextDataSource")));
//
// var newBinary = new IconButton("mdi2c-card-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.RAW);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addBinaryDataSource")));
//
// var newCollection = new IconButton("mdi2b-briefcase-plus-outline", () -> {
// GuiDsCreatorMultiStep.show(StorageViewState.get().selectedGroupProperty().get(),
// DataSourceType.COLLECTION);
// }).apply(JfxHelper.apply(new FancyTooltipAugment<>("addCollectionDataSource")));
var spaceOr = new Region();
spaceOr.setPrefWidth(12);
var box = new HorizontalComp(List.of(newFile, Comp.of(() -> spaceOr), newDb));
box.apply(s -> AppFont.normal(s.get()));
return box;
}
}

View file

@ -1,122 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataFlow;
import javafx.beans.property.*;
import lombok.Value;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Value
public class SourceEntryWrapper implements StorageFilter.Filterable {
DataSourceEntry entry;
StringProperty name = new SimpleStringProperty();
BooleanProperty usable = new SimpleBooleanProperty();
StringProperty information = new SimpleStringProperty();
StringProperty storeSummary = new SimpleStringProperty();
Property<Instant> lastUsed = new SimpleObjectProperty<>();
Property<DataFlow> dataFlow = new SimpleObjectProperty<>();
ObjectProperty<DataSourceEntry.State> state = new SimpleObjectProperty<>();
BooleanProperty loading = new SimpleBooleanProperty();
List<ActionProvider> actionProviders = new ArrayList<>();
public SourceEntryWrapper(DataSourceEntry entry) {
this.entry = entry;
entry.addListener(() -> PlatformThread.runLaterIfNeeded(() -> {
update();
}));
update();
name.addListener((c, o, n) -> {
if (!entry.getName().equals(n)) {
entry.setName(n);
}
});
}
public void moveTo(SourceCollectionWrapper newGroup) {
var old = SourceCollectionViewState.get().getGroup(this);
old.getCollection().removeEntry(this.entry);
newGroup.getCollection().addEntry(this.entry);
}
public void editDialog() {
if (!DataStorage.get().getSourceEntries().contains(entry)) {
return;
}
GuiDsCreatorMultiStep.showEdit(getEntry());
}
public void delete() {
if (!DataStorage.get().getSourceEntries().contains(entry)) {
return;
}
DataStorage.get().deleteSourceEntry(entry);
}
private <T extends DataSource<?>> void update() {
// Avoid reupdating name when changed from the name property!
if (!entry.getName().equals(name.getValue())) {
name.set(entry.getName());
}
lastUsed.setValue(entry.getLastUsed());
state.setValue(entry.getState());
usable.setValue(entry.getState().isUsable());
dataFlow.setValue(entry.getSource() != null ? entry.getSource().getFlow() : null);
storeSummary.setValue(
entry.getState().isUsable()
? DataStoreProviders.byStore(entry.getStore()).toSummaryString(entry.getStore(), 50)
: null);
information.setValue(
entry.getState() != DataSourceEntry.State.LOAD_FAILED
? entry.getInformation() != null
? entry.getInformation()
: entry.getProvider().getDisplayName()
: AppI18n.get("failedToLoad"));
loading.setValue(entry.getState() == null || entry.getState() == DataSourceEntry.State.VALIDATING);
actionProviders.clear();
actionProviders.addAll(ActionProvider.ALL.stream()
.filter(p -> {
try {
if (!entry.getState().isUsable()) {
return false;
}
var c = p.getDataSourceCallSite();
if (c == null) {
return false;
}
return c.getApplicableClass()
.isAssignableFrom(entry.getSource().getClass())
&& c.isApplicable(entry.getSource().asNeeded());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList());
}
@Override
public boolean shouldShow(String filter) {
return getName().get().toLowerCase().contains(filter.toLowerCase());
}
}

View file

@ -1,77 +0,0 @@
package io.xpipe.app.comp.storage.source;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.util.Hyperlinks;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
public class SourceStorageEmptyIntroComp extends SimpleComp {
@Override
public Region createSimple() {
var title = new Label(AppI18n.get("introTitle"));
AppFont.setSize(title, 7);
title.getStyleClass().add("title-header");
var descFi = new FontIcon("mdi2i-information-outline");
var introDesc = new Label(AppI18n.get("introDescription"));
introDesc.heightProperty().addListener((c, o, n) -> {
descFi.iconSizeProperty().set(n.intValue());
});
var fi = new FontIcon("mdi2f-folder-plus-outline");
var addCollection = new Label(AppI18n.get("introCollection"), fi);
addCollection.heightProperty().addListener((c, o, n) -> {
fi.iconSizeProperty().set(n.intValue());
});
var pipeFi = new FontIcon("mdi2p-pipe-disconnected");
var pipe = new Label(AppI18n.get("introPipe"), pipeFi);
pipe.heightProperty().addListener((c, o, n) -> {
pipeFi.iconSizeProperty().set(n.intValue());
});
var dfi = new FontIcon("mdi2b-book-open-variant");
var documentation = new Label(AppI18n.get("introDocumentation"), dfi);
documentation.heightProperty().addListener((c, o, n) -> {
dfi.iconSizeProperty().set(n.intValue());
});
var docLink = new Hyperlink(Hyperlinks.DOCUMENTATION);
docLink.setOnAction(e -> {
Hyperlinks.open(Hyperlinks.DOCUMENTATION);
});
var docLinkPane = new StackPane(docLink);
docLinkPane.setAlignment(Pos.CENTER);
var v = new VBox(
title,
introDesc,
new Separator(Orientation.HORIZONTAL),
addCollection,
new Separator(Orientation.HORIZONTAL),
pipe,
new Separator(Orientation.HORIZONTAL),
documentation,
docLinkPane);
v.setMinWidth(Region.USE_PREF_SIZE);
v.setMaxWidth(Region.USE_PREF_SIZE);
v.setMinHeight(Region.USE_PREF_SIZE);
v.setMaxHeight(Region.USE_PREF_SIZE);
v.setSpacing(10);
v.getStyleClass().add("intro");
var sp = new StackPane(v);
sp.setAlignment(Pos.CENTER);
return sp;
}
}

View file

@ -1,48 +1,29 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import atlantafx.base.controls.Spacer;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.scene.control.Label; import javafx.geometry.Pos;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import lombok.SneakyThrows;
public class DenseStoreEntryComp extends StoreEntryComp { public class DenseStoreEntryComp extends StoreEntryComp {
private final boolean showIcon; private final boolean showIcon;
private final Comp<?> content;
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) { public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
super(entry); super(entry, content);
this.showIcon = showIcon; this.showIcon = showIcon;
this.content = content;
} }
protected Region createContent() { protected Region createContent() {
var name = createName().createRegion(); var name = createName().createRegion();
var size = createInformation();
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
AppFont.small(date);
date.getStyleClass().add("date");
var grid = new GridPane(); var grid = new GridPane();
grid.setHgap(10);
if (showIcon) { if (showIcon) {
var storeIcon = createIcon(30, 25); var storeIcon = createIcon(30, 25);
grid.getColumnConstraints().add(new ColumnConstraints(45)); grid.getColumnConstraints().add(new ColumnConstraints(30));
grid.add(storeIcon, 0, 0); grid.add(storeIcon, 0, 0);
GridPane.setHalignment(storeIcon, HPos.CENTER); GridPane.setHalignment(storeIcon, HPos.CENTER);
} else { } else {
@ -50,60 +31,40 @@ public class DenseStoreEntryComp extends StoreEntryComp {
grid.getColumnConstraints().add(new ColumnConstraints(5)); grid.getColumnConstraints().add(new ColumnConstraints(5));
} }
var fill = new ColumnConstraints(); var custom = new ColumnConstraints(content != null ? 300 : 0);
fill.setHgrow(Priority.ALWAYS); custom.setHalignment(HPos.RIGHT);
grid.getColumnConstraints().addAll(new ColumnConstraints(450), fill); custom.setMinWidth(Region.USE_PREF_SIZE);
custom.setMaxWidth(Region.USE_PREF_SIZE);
var info = new ColumnConstraints(content != null ? 300 : 600);
info.setHalignment(HPos.LEFT);
info.setMinWidth(Region.USE_PREF_SIZE);
info.setMaxWidth(Region.USE_PREF_SIZE);
var nameCC = new ColumnConstraints();
nameCC.setMinWidth(100);
nameCC.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(nameCC);
grid.add(name, 1, 0); grid.add(name, 1, 0);
var c = content != null ? content.createRegion() : new Region(); grid.add(createInformation(), 2, 0);
grid.add(c, 2, 0); grid.getColumnConstraints().addAll(info, custom);
GridPane.setHalignment(c, HPos.CENTER);
var cr = content != null ? content.createRegion() : new Region();
var bb = createButtonBar().createRegion();
var controls = new HBox(cr, bb);
controls.setFillHeight(true);
controls.setAlignment(Pos.CENTER_RIGHT);
controls.setSpacing(10);
HBox.setHgrow(cr, Priority.ALWAYS);
grid.add(controls, 3, 0);
grid.add(createButtonBar().createRegion(), 3, 0, 1, 1);
GrowAugment.create(true, false).augment(grid); GrowAugment.create(true, false).augment(grid);
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-grid"); grid.getStyleClass().add("store-entry-grid");
grid.getStyleClass().add("dense");
applyState(grid); applyState(grid);
return grid;
var button = new JFXButton();
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.getStyleClass().add("condensed-store-entry-comp");
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
HBox.setHgrow(button, Priority.ALWAYS);
new ContextMenuAugment<>(() -> DenseStoreEntryComp.this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
return new HBox(button, new Spacer(25));
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
} }
} }

View file

@ -1,304 +1,60 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList; public class StandardStoreEntryComp extends StoreEntryComp {
public class StandardStoreEntryComp extends SimpleComp { public StandardStoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
super(entry, content);
public static Comp<?> customSection(StoreEntryWrapper e) {
var prov = e.getEntry().getProvider();
if (prov != null) {
return prov.customDisplay(e);
} else {
return new StandardStoreEntryComp(e);
}
} }
private static final double NAME_WIDTH = 0.30;
private static final double STORE_TYPE_WIDTH = 0.08;
private static final double DETAILS_WIDTH = 0.52;
private static final double BUTTONS_WIDTH = 0.1;
private static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
private static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
private final StoreEntryWrapper entry;
public StandardStoreEntryComp(StoreEntryWrapper entry) {
this.entry = entry;
}
private Label createInformation() {
var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
information.getStyleClass().add("information");
AppFont.header(information);
return information;
}
private Label createSummary() {
var summary = new Label();
summary.textProperty().bind(PlatformThread.sync(entry.getSummary()));
summary.getStyleClass().add("summary");
AppFont.small(summary);
return summary;
}
private void applyState(Node node) {
SimpleChangeListener.apply(PlatformThread.sync(entry.getState()), val -> {
switch (val) {
case LOAD_FAILED -> {
node.pseudoClassStateChanged(FAILED, true);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
case INCOMPLETE -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, true);
}
default -> {
node.pseudoClassStateChanged(FAILED, false);
node.pseudoClassStateChanged(INCOMPLETE, false);
}
}
});
}
private Comp<?> createName() {
var name = new LabelComp(entry.nameProperty())
.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
name.apply(s -> AppFont.header(s.get()));
return name;
}
private Node createIcon() {
var img = entry.isDisabled()
? "disabled_icon.png"
: entry.getEntry()
.getProvider()
.getDisplayIconFileName(entry.getEntry().getStore());
var imageComp = new PrettyImageComp(new SimpleStringProperty(img), 55, 45);
var storeIcon = imageComp.createRegion();
storeIcon.getStyleClass().add("icon");
if (entry.getState().getValue().isUsable()) {
new FancyTooltipAugment<>(new SimpleStringProperty(
entry.getEntry().getProvider().getDisplayName()))
.augment(storeIcon);
}
return storeIcon;
}
protected Region createContent() { protected Region createContent() {
var name = createName().createRegion(); var name = createName().createRegion();
var size = createInformation();
var date = new Label();
date.textProperty().bind(AppI18n.readableDuration("usedDate", PlatformThread.sync(entry.lastAccessProperty())));
AppFont.small(date);
date.getStyleClass().add("date");
var grid = new GridPane(); var grid = new GridPane();
grid.setHgap(10);
grid.setVgap(0);
var storeIcon = createIcon(); var storeIcon = createIcon(60, 45);
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
createShareConstraint(grid, DETAILS_WIDTH), createShareConstraint(grid, BUTTONS_WIDTH));
grid.add(storeIcon, 0, 0, 1, 2); grid.add(storeIcon, 0, 0, 1, 2);
grid.add(name, 1, 0); grid.getColumnConstraints().add(new ColumnConstraints(60));
grid.add(date, 1, 1);
grid.add(createSummary(), 2, 1);
grid.add(createInformation(), 2, 0);
grid.add(createButtonBar().createRegion(), 3, 0, 1, 2);
grid.setVgap(5);
GridPane.setHalignment(storeIcon, HPos.CENTER);
AppFont.small(size); grid.add(name, 1, 0);
AppFont.small(date); grid.add(createSummary(), 1, 1);
var nameCC = new ColumnConstraints();
nameCC.setMinWidth(100);
nameCC.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(nameCC);
grid.add(createInformation(), 2, 0, 1, 2);
var info = new ColumnConstraints(content != null ? 300 : 600);
info.setHalignment(HPos.LEFT);
info.setMinWidth(Region.USE_PREF_SIZE);
info.setMaxWidth(Region.USE_PREF_SIZE);
grid.getColumnConstraints().add(info);
var custom = new ColumnConstraints(content != null ? 300 : 0);
custom.setHalignment(HPos.RIGHT);
custom.setMinWidth(Region.USE_PREF_SIZE);
custom.setMaxWidth(Region.USE_PREF_SIZE);
var cr = content != null ? content.createRegion() : new Region();
var bb = createButtonBar().createRegion();
var controls = new HBox(cr, bb);
controls.setFillHeight(true);
HBox.setHgrow(cr, Priority.ALWAYS);
controls.setAlignment(Pos.CENTER_RIGHT);
controls.setSpacing(10);
grid.add(controls, 3, 0, 1, 2);
grid.getColumnConstraints().add(custom);
grid.getStyleClass().add("store-entry-grid"); grid.getStyleClass().add("store-entry-grid");
applyState(grid); applyState(grid);
var button = new JFXButton(); return grid;
button.setGraphic(grid);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(grid));
button.getStyleClass().add("store-entry-comp");
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
new ContextMenuAugment<>(() -> StandardStoreEntryComp.this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
return button;
}
protected Comp<?> createButtonBar() {
var list = new ArrayList<Comp<?>>();
for (var p : entry.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
if (!actionProvider.isMajor()
|| p.getKey().equals(entry.getDefaultActionProvider().getValue())) {
continue;
}
var button = new IconButtonComp(
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
action.execute();
});
});
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
button.hide(Bindings.not(p.getValue()));
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
button.disable(Bindings.not(p.getValue()));
}
list.add(button);
}
var settingsButton = createSettingsButton();
list.add(settingsButton);
return new HorizontalComp(list)
.apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT))
.apply(struc -> {
for (Node child : struc.get().getChildren()) {
((Region) child)
.prefWidthProperty()
.bind((struc.get().heightProperty().divide(1.7)));
((Region) child).prefHeightProperty().bind((struc.get().heightProperty()));
}
});
}
protected Comp<?> createSettingsButton() {
var settingsButton = new IconButtonComp("mdomz-settings");
settingsButton.styleClass("settings");
settingsButton.accessibleText("Settings");
settingsButton.apply(new ContextMenuAugment<>(
event -> event.getButton() == MouseButton.PRIMARY, () -> StandardStoreEntryComp.this.createContextMenu()));
settingsButton.apply(GrowAugment.create(false, true));
settingsButton.apply(s -> {
s.get().prefWidthProperty().bind(Bindings.divide(s.get().heightProperty(), 1.35));
});
settingsButton.apply(new FancyTooltipAugment<>("more"));
return settingsButton;
}
protected ContextMenu createContextMenu() {
var contextMenu = new ContextMenu();
AppFont.normal(contextMenu.getStyleableNode());
for (var p : entry.getActionProviders().entrySet()) {
var actionProvider = p.getKey().getDataStoreCallSite();
if (actionProvider.isMajor()) {
continue;
}
var name = actionProvider.getName(entry.getEntry().getStore().asNeeded());
var icon = actionProvider.getIcon(entry.getEntry().getStore().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
var action = actionProvider.createAction(
entry.getEntry().getStore().asNeeded());
action.execute();
});
});
item.textProperty().bind(name);
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
item.visibleProperty().bind(p.getValue());
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
item.disableProperty().bind(Bindings.not(p.getValue()));
}
contextMenu.getItems().add(item);
}
if (entry.getActionProviders().size() > 0) {
contextMenu.getItems().add(new SeparatorMenuItem());
}
if (AppPrefs.get().developerMode().getValue()) {
var browse = new MenuItem(AppI18n.get("browse"), new FontIcon("mdi2f-folder-open-outline"));
browse.setOnAction(
event -> DesktopHelper.browsePath(entry.getEntry().getDirectory()));
contextMenu.getItems().add(browse);
}
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
refresh.disableProperty().bind(entry.getRefreshable().not());
refresh.setOnAction(event -> {
DataStorage.get().refreshAsync(entry.getEntry(), true);
});
contextMenu.getItems().add(refresh);
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(entry.getDeletable().not());
del.setOnAction(event -> entry.delete());
contextMenu.getItems().add(del);
return contextMenu;
}
protected ColumnConstraints createShareConstraint(Region r, double share) {
var cc = new ColumnConstraints();
cc.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> r.getWidth() * share, r.widthProperty()));
return cc;
}
@SneakyThrows
@Override
protected Region createSimple() {
var loading = new LoadingOverlayComp(Comp.of(() -> createContent()), entry.getLoading());
var region = loading.createRegion();
return region;
} }
} }

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.source.store.GuiDsStoreCreator; import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProvider;

View file

@ -1,12 +1,17 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import com.jfoenix.controls.JFXButton;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment; import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.*; import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
@ -25,6 +30,8 @@ import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
@ -37,22 +44,58 @@ public abstract class StoreEntryComp extends SimpleComp {
if (prov != null) { if (prov != null) {
return prov.customDisplay(e); return prov.customDisplay(e);
} else { } else {
return new StandardStoreEntryComp(e); return new StandardStoreEntryComp(e, null);
} }
} }
public static final double NAME_WIDTH = 0.30;
public static final double STORE_TYPE_WIDTH = 0.08;
public static final double DETAILS_WIDTH = 0.52;
public static final double BUTTONS_WIDTH = 0.1;
public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed"); public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed");
public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete"); public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
protected final StoreEntryWrapper entry; protected final StoreEntryWrapper entry;
protected final Comp<?> content;
public StoreEntryComp(StoreEntryWrapper entry) { public StoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
this.entry = entry; this.entry = entry;
this.content = content;
} }
@Override
protected final Region createSimple() {
var r = createContent();
var button = new JFXButton();
button.setGraphic(r);
GrowAugment.create(true, false).augment(new SimpleCompStructure<>(r));
button.getStyleClass().add("store-entry-comp");
button.setPadding(Insets.EMPTY);
button.setMaxWidth(2000);
button.setFocusTraversable(true);
button.accessibleTextProperty()
.bind(Bindings.createStringBinding(
() -> {
return entry.getName();
},
entry.nameProperty()));
button.accessibleHelpProperty().bind(entry.getInformation());
button.setOnAction(event -> {
event.consume();
ThreadHelper.runFailableAsync(() -> {
entry.refreshIfNeeded();
entry.executeDefaultAction();
});
});
new ContextMenuAugment<>(() -> this.createContextMenu())
.augment(new SimpleCompStructure<>(button));
HBox.setHgrow(button, Priority.ALWAYS);
var hbox = new HBox(button, new Spacer(25));
var loading = new LoadingOverlayComp(Comp.of(() -> hbox), entry.getLoading());
var region = loading.createRegion();
return region;
}
protected abstract Region createContent();
protected Label createInformation() { protected Label createInformation() {
var information = new Label(); var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation())); information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
@ -100,9 +143,6 @@ public abstract class StoreEntryComp extends SimpleComp {
LabelComp name = new LabelComp(Bindings.createStringBinding( LabelComp name = new LabelComp(Bindings.createStringBinding(
() -> { () -> {
return entry.getName() return entry.getName()
+ (entry.getInformation().get() != null
? " [" + entry.getInformation().get() + "]"
: "")
+ (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore + (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore
? " (" + filtered.size() + ")" ? " (" + filtered.size() + ")"
: ""); : "");
@ -130,6 +170,7 @@ public abstract class StoreEntryComp extends SimpleComp {
entry.getEntry().getProvider().getDisplayName())) entry.getEntry().getProvider().getDisplayName()))
.augment(storeIcon); .augment(storeIcon);
} }
storeIcon.setPadding(new Insets(3, 0, 0, 0));
return storeIcon; return storeIcon;
} }
@ -237,7 +278,7 @@ public abstract class StoreEntryComp extends SimpleComp {
}); });
contextMenu.getItems().add(refresh); contextMenu.getItems().add(refresh);
var del = new MenuItem(AppI18n.get("delete"), new FontIcon("mdal-delete_outline")); var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
del.disableProperty().bind(entry.getDeletable().not()); del.disableProperty().bind(entry.getDeletable().not());
del.setOnAction(event -> entry.delete()); del.setOnAction(event -> entry.delete());
contextMenu.getItems().add(del); contextMenu.getItems().add(del);

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.MultiContentComp; import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.core.AppState; import io.xpipe.app.core.AppState;
@ -23,9 +22,9 @@ public class StoreEntryListComp extends SimpleComp {
.getFilterString() .getFilterString()
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s)))); .map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> { var content = new ListBoxViewComp<>(filtered, topLevel.getChildren(), (StoreSection e) -> {
return StoreSection.customSection(e); return StoreSection.customSection(e).styleClass("top");
}); });
return content.styleClass("store-list-comp").styleClass(Styles.STRIPED); return content.styleClass("store-list-comp");
} }
@Override @Override

View file

@ -35,7 +35,7 @@ public class StoreEntrySectionComp extends Comp<CompStructure<VBox>> {
() -> { () -> {
section.getWrapper().toggleExpanded(); section.getWrapper().toggleExpanded();
}) })
.apply(struc -> struc.get().setPrefWidth(40)) .apply(struc -> struc.get().setPrefWidth(30))
.focusTraversable() .focusTraversable()
.accessibleText("Expand") .accessibleText("Expand")
.disable(BindingsHelper.persist( .disable(BindingsHelper.persist(
@ -69,6 +69,7 @@ public class StoreEntrySectionComp extends Comp<CompStructure<VBox>> {
.hide(BindingsHelper.persist(Bindings.or( .hide(BindingsHelper.persist(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()), Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getChildren()).isEqualTo(0)))))) Bindings.size(section.getChildren()).isEqualTo(0))))))
.styleClass("store-entry-section-comp")
.createStructure(); .createStructure();
} }
} }

View file

@ -1,6 +1,6 @@
package io.xpipe.app.comp.storage.store; package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.source.store.GuiDsStoreCreator; import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.comp.storage.StorageFilter; import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
@ -177,7 +177,12 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
entry.updateLastUsed(); entry.updateLastUsed();
found.createAction(entry.getStore().asNeeded()).execute(); found.createAction(entry.getStore().asNeeded()).execute();
} else if (getEntry().getStore() instanceof FixedHierarchyStore) { } else if (getEntry().getStore() instanceof FixedHierarchyStore) {
DataStorage.get().refreshChildren(entry); var hasChildren = DataStorage.get().refreshChildren(entry);
if (!hasChildren) {
PlatformThread.runLaterIfNeeded(() -> {
expanded.set(false);
});
}
} }
} }

View file

@ -29,7 +29,7 @@ public class StoreSection implements StorageFilter.Filterable {
private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing( private static final Comparator<StoreSection> COMPARATOR = Comparator.<StoreSection, Instant>comparing(
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID) o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
? o.wrapper.getEntry().getLastAccess() ? o.wrapper.getEntry().getLastModified()
: Instant.EPOCH) : Instant.EPOCH)
.reversed() .reversed()
.thenComparing( .thenComparing(
@ -66,7 +66,7 @@ public class StoreSection implements StorageFilter.Filterable {
.getStore() .getStore()
.equals(other.getEntry() .equals(other.getEntry()
.getProvider() .getProvider()
.getLogicalParent(other.getEntry().getStore()))); .getDisplayParent(other.getEntry().getStore())));
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1)); var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR); var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
return new StoreSection(e, ordered); return new StoreSection(e, ordered);

View file

@ -2,7 +2,6 @@ package io.xpipe.app.comp.storage.store;
import io.xpipe.app.comp.storage.StorageFilter; import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener; import io.xpipe.app.storage.StorageListener;
@ -68,12 +67,6 @@ public class StoreViewState {
allEntries.removeIf(e -> e.getEntry().equals(entry)); allEntries.removeIf(e -> e.getEntry().equals(entry));
}); });
} }
@Override
public void onCollectionAdd(DataSourceCollection collection) {}
@Override
public void onCollectionRemove(DataSourceCollection collection) {}
}); });
} }

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppCache;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.browser.StandaloneFileBrowser; import io.xpipe.app.browser.StandaloneFileBrowser;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.DataStoreProvider;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.FileDropOverlayComp; import io.xpipe.app.comp.base.FileDropOverlayComp;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
@ -12,11 +12,11 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.TabPaneComp; import io.xpipe.app.fxcomps.impl.TabPaneComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener; import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.SimpleValidator; import io.xpipe.app.util.SimpleValidator;
import io.xpipe.app.util.Validatable; import io.xpipe.app.util.Validatable;
import io.xpipe.app.util.Validator; import io.xpipe.app.util.Validator;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore; import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore; import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
@ -70,7 +70,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
@Override @Override
protected Region createSimple() { protected Region createSimple() {
var isNamedStore = var isNamedStore =
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent(); DataStorage.get().getStoreDisplayName(selected.getValue()).isPresent();
var localStore = new SimpleObjectProperty<>( var localStore = new SimpleObjectProperty<>(
!isNamedStore !isNamedStore
&& selected.getValue() instanceof FileStore fileStore && selected.getValue() instanceof FileStore fileStore

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.ErrorOverlayComp; import io.xpipe.app.comp.base.ErrorOverlayComp;
import io.xpipe.app.comp.base.MultiStepComp; import io.xpipe.app.comp.base.MultiStepComp;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.comp.source.store; package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListViewComp; import io.xpipe.app.comp.base.ListViewComp;

View file

@ -1,7 +1,6 @@
package io.xpipe.app.core.mode; package io.xpipe.app.core.mode;
import io.xpipe.app.browser.BrowserModel; import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState; import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*; import io.xpipe.app.core.*;
import io.xpipe.app.issue.*; import io.xpipe.app.issue.*;
@ -55,7 +54,6 @@ public class BaseMode extends OperationMode {
TrackEvent.info("mode", "Background mode shutdown started"); TrackEvent.info("mode", "Background mode shutdown started");
BrowserModel.DEFAULT.reset(); BrowserModel.DEFAULT.reset();
AppSocketServer.reset(); AppSocketServer.reset();
SourceCollectionViewState.reset();
StoreViewState.reset(); StoreViewState.reset();
DataStorage.reset(); DataStorage.reset();
AppPrefs.reset(); AppPrefs.reset();

View file

@ -1,6 +1,5 @@
package io.xpipe.app.core.mode; package io.xpipe.app.core.mode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState; import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*; import io.xpipe.app.core.*;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
@ -83,7 +82,6 @@ public abstract class PlatformMode extends OperationMode {
UpdateAvailableAlert.showIfNeeded(); UpdateAvailableAlert.showIfNeeded();
} }
SourceCollectionViewState.init();
StoreViewState.init(); StoreViewState.init();
} }

View file

@ -1,70 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.ConvertExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.source.DataSource;
public class ConvertExchangeImpl extends ConvertExchange
implements MessageExchangeImpl<ConvertExchange.Request, ConvertExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), null, false);
DataSourceProvider<?> newProvider;
DataSource<?> newSource;
if (msg.getNewProvider() != null && msg.getNewCategory() != null) {
var provider = getProvider(msg.getNewProvider());
if (!provider.getPrimaryType().equals(msg.getNewCategory())) {
throw new ClientException("Incompatible types: " + provider.getId() + " and "
+ msg.getNewCategory().name().toLowerCase());
}
newProvider = provider;
newSource = newProvider.createDefaultSource(ds.getStore().asNeeded());
} else if (msg.getNewProvider() != null) {
if (msg.getNewProvider().equals(ds.getProvider().getId())) {
return ConvertExchange.Response.builder().build();
}
newProvider = getProvider(msg.getNewProvider());
newSource = newProvider.createDefaultSource(ds.getStore().asNeeded());
} else if (msg.getNewCategory() != null) {
if (msg.getNewCategory().equals(ds.getProvider().getPrimaryType())) {
return ConvertExchange.Response.builder().build();
}
var provider = ds.getProvider();
DataSource<?> s = ds.getSource();
if (!ds.getProvider().supportsConversion(s.asNeeded(), msg.getNewCategory())) {
throw new ClientException(
"Data source of type " + provider.getId() + " can not be converted to category "
+ msg.getNewCategory().name().toLowerCase());
}
newSource = provider.convert(s.asNeeded(), msg.getNewCategory());
newProvider = DataSourceProviders.byDataSourceClass(newSource.getClass());
} else {
throw new ClientException("No data format or data type specified");
}
var dialog = toCompleteConfig(newSource, newProvider, true);
var id = DataStorage.get().getId(ds);
var sent = Dialog.chain(
dialog,
Dialog.header("Successfully converted " + (id != null ? id : "source") + " to "
+ newProvider.getDisplayName()))
.evaluateTo(dialog);
var ref = DialogExchangeImpl.add(sent, (DataSource<?> o) -> {
ds.setSource(o);
DataStorage.get().save();
});
return ConvertExchange.Response.builder().config(ref).build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.EditExchange;
import io.xpipe.core.source.DataSource;
public class EditExchangeImpl extends EditExchange
implements MessageExchangeImpl<EditExchange.Request, EditExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getRef(), null, false);
var provider = source.getProvider();
var dialog = toCompleteConfig(source.getSource().asNeeded(), provider, true);
var config = DialogExchangeImpl.add(dialog, (DataSource<?> o) -> {
source.setSource(o);
});
return Response.builder()
.config(config)
.id(DataStorage.get().getId(source))
.build();
}
}

View file

@ -2,8 +2,6 @@ package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider; import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders; import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconHandler;
@ -14,9 +12,6 @@ import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.impl.NamedStore; import io.xpipe.core.impl.NamedStore;
import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import lombok.NonNull; import lombok.NonNull;
@ -68,42 +63,6 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
return store.get(); return store.get();
} }
default DataSourceCollection getCollection(String name) throws ClientException {
var col = DataStorage.get().getCollectionForName(name);
if (col.isEmpty()) {
throw new ClientException("No collection with name " + name + " was found");
}
return col.get();
}
default DataSourceEntry getSourceEntry(DataSourceReference ref, DataSourceType typeFilter, boolean acceptDisabled)
throws ClientException {
var ds = DataStorage.get().getDataSource(ref);
if (ds.isEmpty() && ref.getType() == DataSourceReference.Type.LATEST) {
throw new ClientException("No latest data source available");
}
if (ds.isEmpty()) {
throw new ClientException("Unable to locate data source with reference " + ref.toRefString());
}
if (typeFilter != null && ds.get().getProvider().getPrimaryType() != typeFilter) {
throw new ClientException(
"Data source is not a " + typeFilter.name().toLowerCase());
}
if (!ds.get().getState().isUsable() && !acceptDisabled) {
throw new ClientException(
String.format("Data source %s is disabled", ds.get().getName()));
}
return ds.get();
}
default DataSourceEntry getSourceEntry(DataSourceId id, DataSourceType typeFilter, boolean acceptDisabled)
throws ClientException {
return getSourceEntry(DataSourceReference.id(id), typeFilter, acceptDisabled);
}
String getId(); String getId();
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception; RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;

View file

@ -1,27 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.QueryDataSourceExchange;
import io.xpipe.core.dialog.DialogMapper;
public class QueryDataSourceExchangeImpl extends QueryDataSourceExchange
implements MessageExchangeImpl<QueryDataSourceExchange.Request, QueryDataSourceExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getRef(), null, false);
var id = DataStorage.get().getId(source);
var information = source.getInformation();
var dialog = source.getProvider().configDialog(source.getSource().asNeeded(), true);
var config = new DialogMapper(dialog).handle();
return QueryDataSourceExchange.Response.builder()
.id(id)
.type(source.getDataSourceType())
.information(information)
.internalSource(source.getSource())
.provider(source.getProvider().getId())
.config(config)
.build();
}
}

View file

@ -2,14 +2,11 @@ package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider; import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders; import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler; import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException; import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.ReadExchange; import io.xpipe.beacon.exchange.ReadExchange;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter; import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.source.DataSource;
import java.util.UUID; import java.util.UUID;
@ -54,11 +51,6 @@ public class ReadExchangeImpl extends ReadExchange
var entryName = var entryName =
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName(); noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
var configRef = DialogExchangeImpl.add(Dialog.chain(typeQ, config), (DataSource<?> s) -> { return Response.builder().build();
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, s);
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
});
return Response.builder().config(configRef).build();
} }
} }

View file

@ -1,22 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.SelectExchange;
import io.xpipe.core.source.DataSourceReference;
public class SelectExchangeImpl extends SelectExchange
implements MessageExchangeImpl<SelectExchange.Request, SelectExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
if (msg.getRef().getType() == DataSourceReference.Type.LATEST) {
throw new ClientException("Can't select latest data source");
}
var ds = getSourceEntry(msg.getRef(), null, false);
DataStorage.get().setLatest(ds);
return Response.builder().build();
}
}

View file

@ -1,50 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.WriteExecuteExchange;
import io.xpipe.core.impl.OutputStreamStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.WriteMode;
import io.xpipe.core.store.StreamDataStore;
public class WriteExecuteExchangeImpl extends WriteExecuteExchange
implements MessageExchangeImpl<WriteExecuteExchange.Request, WriteExecuteExchange.Response> {
@Override
@SuppressWarnings("unchecked")
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), null, false);
var target = WritePreparationExchangeImpl.CONFIGS.remove(msg.getId());
if (target.getType() != ds.getSource().getType()) {
throw new ClientException("Incompatible data source types");
}
var local = target.getStore() instanceof StreamDataStore s && s.isContentExclusivelyAccessible();
var mode = msg.getMode() != null ? WriteMode.byId(msg.getMode()) : WriteMode.REPLACE;
if (local) {
handler.postResponse(() -> {
var usedStore = new OutputStreamStore(handler.sendBody());
var out = ((DataSource<StreamDataStore>) target).withStore(usedStore);
try (var con = ds.getSource().openReadConnection();
var outCon = out.openWriteConnection(mode)) {
con.init();
outCon.init();
con.forward(outCon);
}
});
return WriteExecuteExchange.Response.builder().hasBody(true).build();
} else {
var out = target;
try (var con = ds.getSource().openReadConnection();
var outCon = out.openWriteConnection(mode)) {
con.init();
outCon.init();
con.forward(outCon);
}
return WriteExecuteExchange.Response.builder().build();
}
}
}

View file

@ -1,62 +0,0 @@
package io.xpipe.app.exchange;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.WritePreparationExchange;
import io.xpipe.core.source.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class WritePreparationExchangeImpl extends WritePreparationExchange
implements MessageExchangeImpl<WritePreparationExchange.Request, WritePreparationExchange.Response> {
public static final Map<UUID, DataSource<?>> CONFIGS = new HashMap<>();
@Override
public WritePreparationExchange.Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var store = resolveStore(msg.getOutputStore(), false);
var provider = DataSourceProviders.byPreferredStore(store, null);
DataSourceProvider<?> outputProvider;
DataSource<?> source;
if (msg.getOutputSource() == null) {
if (msg.getType() == null && provider.isEmpty()) {
var entry = getSourceEntry(msg.getSource(), null, false);
var sourceType = entry.getProvider();
if (sourceType.couldSupportStore(msg.getOutputStore())) {
outputProvider = sourceType;
} else {
throw new ClientException("Missing output type");
}
} else if (msg.getType() != null) {
outputProvider = DataSourceProviders.byName(msg.getType())
.orElseThrow(() -> new ClientException("Unknown output format type: " + msg.getType()));
} else if (provider.isPresent()) {
outputProvider = provider.get();
} else {
throw new IllegalStateException();
}
if (!outputProvider.couldSupportStore(store)) {
throw new ClientException("Unsupported store type");
}
source = outputProvider.createDefaultSource(store);
} else {
source = getSourceEntry(msg.getOutputSource(), null, false).getSource();
outputProvider = DataSourceProviders.byDataSourceClass(source.getClass());
}
var id = UUID.randomUUID();
var config = toCompleteConfig(source, outputProvider, false);
var configRef = DialogExchangeImpl.add(config, id, (DataSource<?> s) -> {
CONFIGS.put(id, s);
});
return Response.builder().config(configRef).build();
}
}

View file

@ -1,28 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.AddSourceExchange;
import java.util.UUID;
public class AddSourceExchangeImpl extends AddSourceExchange
implements MessageExchangeImpl<AddSourceExchange.Request, AddSourceExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var noTarget = msg.getTarget() == null;
var colName = noTarget ? null : msg.getTarget().getCollectionName();
var entryName =
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, msg.getSource());
entry.refresh(true);
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
return Response.builder().id(DataStorage.get().getId(entry)).build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.ForwardExchange;
import io.xpipe.core.source.WriteMode;
public class ForwardExchangeImpl extends ForwardExchange
implements MessageExchangeImpl<ForwardExchange.Request, ForwardExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var source = getSourceEntry(msg.getSource(), null, false);
var target = getSourceEntry(msg.getTarget(), null, false);
try (var con = source.getSource().openReadConnection();
var outCon =
target.getSource().openWriteConnection(msg.isAppend() ? WriteMode.APPEND : WriteMode.REPLACE)) {
con.init();
outCon.init();
con.forward(outCon);
}
return ForwardExchange.Response.builder().build();
}
}

View file

@ -1,25 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.RawReadConnection;
public class QueryRawDataExchangeImpl extends QueryRawDataExchange
implements MessageExchangeImpl<QueryRawDataExchange.Request, QueryRawDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.RAW, false);
handler.postResponse(() -> {
try (var out = handler.sendBody()) {
try (var con = (RawReadConnection) ds.getSource().openReadConnection()) {
con.init();
con.forwardBytes(out, msg.getMaxBytes());
}
}
});
return Response.builder().build();
}
}

View file

@ -1,39 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.TableReadConnection;
public class QueryTableDataExchangeImpl extends QueryTableDataExchange
implements MessageExchangeImpl<QueryTableDataExchange.Request, QueryTableDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.TABLE, false);
TableReadConnection readConnection = null;
TupleType dataType;
try {
readConnection = (TableReadConnection) ds.getDataSource().openReadConnection();
readConnection.init();
dataType = readConnection.getDataType();
TableReadConnection finalReadConnection = readConnection;
handler.postResponse(() -> {
try (var out = handler.sendBody()) {
try (var con = finalReadConnection) {
con.forwardRows(out, msg.getMaxRows());
}
}
});
} catch (Exception ex) {
if (readConnection != null) {
readConnection.close();
}
throw ex;
}
return QueryTableDataExchange.Response.builder().dataType(dataType).build();
}
}

View file

@ -1,31 +0,0 @@
package io.xpipe.app.exchange.api;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.source.TextReadConnection;
import java.nio.charset.StandardCharsets;
public class QueryTextDataExchangeImpl extends QueryTextDataExchange
implements MessageExchangeImpl<QueryTextDataExchange.Request, QueryTextDataExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var ds = getSourceEntry(msg.getRef(), DataSourceType.TEXT, false);
handler.postResponse(() -> {
try (var out = handler.sendBody();
var con = ((TextReadConnection) ds.getSource().openReadConnection())) {
con.init();
try (var stream = con.lines().limit(msg.getMaxLines() == -1 ? Long.MAX_VALUE : msg.getMaxLines())) {
for (var s : stream.toList()) {
out.write(s.getBytes(StandardCharsets.UTF_8));
out.write("\n".getBytes(StandardCharsets.UTF_8));
}
}
}
});
return QueryTextDataExchange.Response.builder().build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.ListCollectionsExchange;
import io.xpipe.beacon.exchange.data.CollectionListEntry;
public class ListCollectionsExchangeImpl extends ListCollectionsExchange
implements MessageExchangeImpl<ListCollectionsExchange.Request, ListCollectionsExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
DataStorage s = DataStorage.get();
var e = s.getSourceCollections().stream()
.map(col -> CollectionListEntry.builder()
.name(col.getName())
.size(col.getEntries().size())
.lastUsed(col.getLastAccess())
.build())
.toList();
return Response.builder().entries(e).build();
}
}

View file

@ -1,37 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.cli.ListEntriesExchange;
import io.xpipe.beacon.exchange.data.EntryListEntry;
import java.util.Optional;
public class ListEntriesExchangeImpl extends ListEntriesExchange
implements MessageExchangeImpl<ListEntriesExchange.Request, ListEntriesExchange.Response> {
@Override
public ListEntriesExchange.Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
DataStorage s = DataStorage.get();
var col = s.getCollectionForName(msg.getCollection())
.or(() -> Optional.ofNullable(
msg.getCollection().equalsIgnoreCase("temporary") ? s.getInternalCollection() : null));
if (col.isEmpty()) {
throw new ClientException("No collection with name " + msg.getCollection() + " was found");
}
var list = col.get().getEntries().stream()
.map(e -> {
return EntryListEntry.builder()
.name(e.getName())
.type(e.getProvider().getId())
.description("")
.lastUsed(e.getLastUsed())
.build();
})
.toList();
return Response.builder().entries(list).build();
}
}

View file

@ -1,17 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RemoveCollectionExchange;
public class RemoveCollectionExchangeImpl extends RemoveCollectionExchange
implements MessageExchangeImpl<RemoveCollectionExchange.Request, RemoveCollectionExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var col = getCollection(msg.getCollectionName());
DataStorage.get().deleteCollection(col);
return Response.builder().build();
}
}

View file

@ -1,18 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RemoveEntryExchange;
public class RemoveEntryExchangeImpl extends RemoveEntryExchange
implements MessageExchangeImpl<RemoveEntryExchange.Request, RemoveEntryExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var e = getSourceEntry(msg.getRef(), null, true);
var id = DataStorage.get().getId(e);
DataStorage.get().deleteSourceEntry(e);
return Response.builder().id(id).build();
}
}

View file

@ -1,16 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RenameCollectionExchange;
public class RenameCollectionExchangeImpl extends RenameCollectionExchange
implements MessageExchangeImpl<RenameCollectionExchange.Request, RenameCollectionExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var col = getCollection(msg.getCollectionName());
col.setName(msg.getNewName());
return Response.builder().build();
}
}

View file

@ -1,24 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.RenameEntryExchange;
public class RenameEntryExchangeImpl extends RenameEntryExchange
implements MessageExchangeImpl<RenameEntryExchange.Request, RenameEntryExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
var e = getSourceEntry(msg.getRef(), null, true);
DataStorage.get().deleteSourceEntry(e);
DataStorage.get()
.add(
e,
DataStorage.get()
.getCollectionForName(msg.getNewId().getCollectionName())
.orElseThrow());
e.setName(msg.getNewId().getEntryName());
return Response.builder().build();
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.SourceProviderListExchange;
import io.xpipe.beacon.exchange.data.ProviderEntry;
import io.xpipe.core.source.DataSourceType;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class SourceProviderListExchangeImpl extends SourceProviderListExchange
implements MessageExchangeImpl<SourceProviderListExchange.Request, SourceProviderListExchange.Response> {
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
var all = DataSourceProviders.getAll();
var map = new LinkedHashMap<DataSourceType, List<ProviderEntry>>();
for (DataSourceType t : DataSourceType.values()) {
map.put(t, new ArrayList<>());
}
for (var p : all) {
map.get(p.getPrimaryType())
.add(ProviderEntry.builder()
.id(p.getPossibleNames().get(0))
.description(p.getDisplayDescription())
.build());
}
return Response.builder().entries(map).build();
}
}

View file

@ -33,7 +33,7 @@ public interface DataStoreProvider {
} }
default Comp<?> customDisplay(StoreEntryWrapper w) { default Comp<?> customDisplay(StoreEntryWrapper w) {
return new StandardStoreEntryComp(w); return new StandardStoreEntryComp(w, null);
} }
default Comp<?> customContainer(StoreSection section) { default Comp<?> customContainer(StoreSection section) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.ext;
import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.ModuleLayerLoader;
@ -12,7 +11,8 @@ import io.xpipe.core.util.ProxyFunction;
public class XPipeServiceProviders { public class XPipeServiceProviders {
public static void load(ModuleLayer layer) { public static void load(ModuleLayer layer) {
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent(); // TODO
var hasDaemon = true;
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> { ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
ErrorEvent.fromThrowable(t).handle(); ErrorEvent.fromThrowable(t).handle();
}); });

View file

@ -6,7 +6,6 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder; import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LeafShellStore; import io.xpipe.core.store.LeafShellStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
@ -70,7 +69,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return Optional.of(AppI18n.get("none")); return Optional.of(AppI18n.get("none"));
} }
return XPipeDaemon.getInstance().getStoreName(store); return DataStorage.get().getStoreDisplayName(store);
}) })
.orElse(AppI18n.get("unknown")); .orElse(AppI18n.get("unknown"));
@ -82,7 +81,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return AppI18n.get("none"); return AppI18n.get("none");
} }
return XPipeDaemon.getInstance().getStoreName(store).orElse("?"); return DataStorage.get().getStoreDisplayName(store).orElse("?");
} }
@Override @Override

View file

@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder; import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore; import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property; import javafx.beans.property.Property;
@ -27,7 +26,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
var name = DataStorage.get().getUsableStores().stream() var name = DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(store)) .filter(e -> e.equals(store))
.findAny() .findAny()
.map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?")) .map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
.orElse("?"); .orElse("?");
return name; return name;
} }

View file

@ -1,10 +0,0 @@
package io.xpipe.app.storage;
public interface CollectionListener {
void onUpdate();
void onEntryAdd(DataSourceEntry entry);
void onEntryRemove(DataSourceEntry entry);
}

View file

@ -1,141 +0,0 @@
package io.xpipe.app.storage;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.core.util.JacksonMapper;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
public class DataSourceCollection extends StorageElement {
private final Map<UUID, DataSourceEntry> entries;
private final List<CollectionListener> listeners;
private DataSourceCollection(
Path directory,
UUID uuid,
String name,
Instant lastUsed,
Instant lastModified,
Map<UUID, DataSourceEntry> entries,
boolean dirty) {
super(directory, uuid, name, lastUsed, lastModified, dirty);
this.entries = new LinkedHashMap<>(entries);
this.listeners = new ArrayList<>();
this.listeners.add(new CollectionListener() {
@Override
public void onUpdate() {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
@Override
public void onEntryAdd(DataSourceEntry entry) {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
@Override
public void onEntryRemove(DataSourceEntry entry) {
DataSourceCollection.this.dirty = true;
DataSourceCollection.this.lastModified = Instant.now();
}
});
}
public static DataSourceCollection createNew(String name) {
var c = new DataSourceCollection(null, UUID.randomUUID(), name, null, Instant.now(), Map.of(), true);
return c;
}
public static DataSourceCollection fromDirectory(DataStorage storage, Path dir) throws Exception {
ObjectMapper mapper = JacksonMapper.newMapper();
var json = mapper.readTree(dir.resolve("collection.json").toFile());
var uuid = UUID.fromString(json.required("uuid").textValue());
var name = json.required("name").textValue();
Objects.requireNonNull(name);
var lastModified = Instant.parse(json.required("lastModified").textValue());
JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, UUID.class);
var entries = new LinkedHashMap<UUID, DataSourceEntry>();
for (var u : mapper.<List<UUID>>readValue(dir.resolve("entries.json").toFile(), listType)) {
var v = storage.getSourceEntry(u).orElse(null);
entries.put(u, v);
}
Instant lastUsed = entries.values().stream()
.filter(Objects::nonNull)
.map(DataSourceEntry::getLastUsed)
.max(Comparator.naturalOrder())
.orElse(null);
return new DataSourceCollection(dir, uuid, name, lastUsed, lastModified, entries, false);
}
public void writeDataToDisk() throws Exception {
if (!dirty) {
return;
}
ObjectNode obj = JsonNodeFactory.instance.objectNode();
obj.put("uuid", uuid.toString());
obj.put("name", name);
obj.put("lastModified", lastModified.toString());
ObjectMapper mapper = JacksonMapper.newMapper();
var collectionString = mapper.writeValueAsString(obj);
var entriesString = mapper.writeValueAsString(entries.keySet().stream().toList());
FileUtils.forceMkdir(directory.toFile());
Files.writeString(directory.resolve("collection.json"), collectionString);
Files.writeString(directory.resolve("entries.json"), entriesString);
this.dirty = false;
}
public void addListener(CollectionListener l) {
this.listeners.add(l);
}
public void addEntry(DataSourceEntry e) {
this.entries.put(e.getUuid(), e);
this.listeners.forEach(l -> l.onEntryAdd(e));
}
public void removeEntry(DataSourceEntry e) {
this.entries.remove(e.getUuid());
this.listeners.forEach(l -> l.onEntryRemove(e));
}
@Override
public void refresh(boolean deep) {}
public void setName(String name) {
if (name.equals(this.name)) {
return;
}
var oldName = this.name;
this.name = name;
this.listeners.forEach(l -> l.onUpdate());
}
@Override
protected boolean shouldSave() {
return true;
}
public void clear() {
entries.clear();
}
public List<DataSourceEntry> getEntries() {
return entries.values().stream().filter(Objects::nonNull).toList();
}
}

View file

@ -1,246 +0,0 @@
package io.xpipe.app.storage;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataSourceProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonMapper;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.Value;
import lombok.experimental.NonFinal;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.UUID;
@Value
@EqualsAndHashCode(callSuper = true)
public class DataSourceEntry extends StorageElement {
@NonFinal
State state;
@NonFinal
String information;
@NonFinal
JsonNode sourceNode;
@NonFinal
DataSource<?> dataSource;
private DataSourceEntry(
Path directory,
UUID uuid,
String name,
Instant lastUsed,
Instant lastModified,
boolean dirty,
JsonNode sourceNode,
String info,
State state) {
super(directory, uuid, name, lastUsed, lastModified, dirty);
this.sourceNode = sourceNode;
this.dataSource = DataStorageParser.sourceFromNode(sourceNode);
this.information = info;
this.state = state;
}
public static DataSourceEntry createExisting(
@NonNull Path directory,
@NonNull UUID uuid,
@NonNull String name,
@NonNull Instant lastUsed,
@NonNull Instant lastModified,
JsonNode sourceNode,
String info,
@NonNull State state)
throws Exception {
var entry = new DataSourceEntry(directory, uuid, name, lastUsed, lastModified, false, sourceNode, info, state);
entry.refresh(false);
return entry;
}
public static DataSourceEntry createNew(
@NonNull UUID uuid, @NonNull String name, @NonNull DataSource<?> dataSource) {
var entry = new DataSourceEntry(
null,
uuid,
name,
Instant.now(),
Instant.now(),
true,
DataStorageWriter.sourceToNode(dataSource),
null,
State.INCOMPLETE);
entry.simpleRefresh();
return entry;
}
public static DataSourceEntry fromDirectory(Path dir) throws Exception {
ObjectMapper mapper = JacksonMapper.newMapper();
var entryFile = dir.resolve("entry.json");
var sourceFile = dir.resolve("source.json");
if (!Files.exists(entryFile) || !Files.exists(sourceFile)) {
return null;
}
var json = mapper.readTree(entryFile.toFile());
var uuid = UUID.fromString(json.required("uuid").textValue());
var name = json.required("name").textValue();
var info = json.required("info").textValue();
var lastUsed = Instant.parse(json.required("lastUsed").textValue());
var lastModified = Instant.parse(json.required("lastModified").textValue());
var state = mapper.treeToValue(json.get("state"), State.class);
if (state == null) {
state = State.INCOMPLETE;
}
// Source loading is prone to errors.
// Therefore, handle invalid source in a special way
JsonNode sourceNode = null;
try {
sourceNode = mapper.readTree(sourceFile.toFile());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
return createExisting(dir, uuid, name, lastUsed, lastModified, sourceNode, info, state);
}
public void writeDataToDisk() throws Exception {
if (!dirty) {
return;
}
ObjectMapper mapper = JacksonMapper.newMapper();
ObjectNode obj = JsonNodeFactory.instance.objectNode();
obj.put("uuid", uuid.toString());
obj.put("info", information);
obj.put("name", name);
obj.put("lastUsed", lastUsed.toString());
obj.put("lastModified", lastModified.toString());
var entryString = mapper.writeValueAsString(obj);
var sourceString = mapper.writeValueAsString(dataSource);
FileUtils.forceMkdir(directory.toFile());
Files.writeString(directory.resolve("entry.json"), entryString);
Files.writeString(directory.resolve("source.json"), sourceString);
this.dirty = false;
}
public void refresh(boolean deep) throws Exception {
var oldSource = dataSource;
DataSource<?> newSource = DataStorageParser.sourceFromNode(sourceNode);
// TrackEvent.storage()
// .trace()
// .message("Refreshing data source entry")
// .tag("old", oldSource)
// .tag("new", newSource)
// .handle();
if (newSource == null) {
dataSource = null;
state = State.LOAD_FAILED;
information = null;
dirty = oldSource != null;
propagateUpdate();
return;
}
dataSource = newSource;
dirty = true;
if (state == State.LOAD_FAILED || state == State.INCOMPLETE) {
state = newSource.isComplete() ? State.COMPLETE_BUT_INVALID : State.INCOMPLETE;
propagateUpdate();
}
if (!deep || !dataSource.isComplete()) {
return;
}
try {
state = State.VALIDATING;
propagateUpdate();
dataSource.validate();
state = State.COMPLETE_AND_VALID;
information = getProvider().queryInformationString(getStore(), 50);
propagateUpdate();
} catch (Exception e) {
state = dataSource.isComplete() ? State.COMPLETE_BUT_INVALID : State.INCOMPLETE;
information = null;
propagateUpdate();
throw e;
}
}
@Override
protected boolean shouldSave() {
return true;
}
public DataSourceProvider<?> getProvider() {
if (state == State.LOAD_FAILED) {
return null;
}
return DataSourceProviders.byDataSourceClass(dataSource.getClass());
}
public DataSourceType getDataSourceType() {
return getProvider() != null ? getProvider().getPrimaryType() : null;
}
public DataStore getStore() {
return dataSource.getStore();
}
public DataSource<?> getSource() {
return dataSource;
}
public void setSource(DataSource<?> dataSource) {
this.dirty = true;
this.dataSource = dataSource;
this.sourceNode = DataStorageWriter.sourceToNode(dataSource);
propagateUpdate();
}
@Getter
public enum State {
@JsonProperty("loadFailed")
LOAD_FAILED(false),
@JsonProperty("incomplete")
INCOMPLETE(false),
@JsonProperty("completeButInvalid")
COMPLETE_BUT_INVALID(true),
@JsonProperty("validating")
VALIDATING(true),
@JsonProperty("completeAndValid")
COMPLETE_AND_VALID(true);
private final boolean isUsable;
State(boolean isUsable) {
this.isUsable = isUsable;
}
}
}

View file

@ -1,16 +1,9 @@
package io.xpipe.app.storage; package io.xpipe.app.storage;
import io.xpipe.app.core.AppCharsets;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.charsetter.Charsettable;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FixedHierarchyStore; import io.xpipe.core.store.FixedHierarchyStore;
import lombok.Getter; import lombok.Getter;
@ -26,19 +19,13 @@ public abstract class DataStorage {
private static DataStorage INSTANCE; private static DataStorage INSTANCE;
protected final Path dir; protected final Path dir;
protected final List<DataSourceEntry> sourceEntries;
protected final List<DataSourceCollection> sourceCollections;
protected final List<DataStoreEntry> storeEntries; protected final List<DataStoreEntry> storeEntries;
@Getter @Getter
private final List<StorageListener> listeners = new ArrayList<>(); private final List<StorageListener> listeners = new ArrayList<>();
private DataSourceEntry latest;
public DataStorage() { public DataStorage() {
this.dir = AppPrefs.get().storageDirectory().getValue(); this.dir = AppPrefs.get().storageDirectory().getValue();
this.sourceEntries = new ArrayList<>();
this.sourceCollections = new ArrayList<>();
this.storeEntries = new ArrayList<>(); this.storeEntries = new ArrayList<>();
} }
@ -80,30 +67,13 @@ public abstract class DataStorage {
return INSTANCE; return INSTANCE;
} }
public synchronized DataSourceCollection getInternalCollection() { public synchronized boolean refreshChildren(DataStoreEntry e) {
var found = sourceCollections.stream() return refreshChildren(e, List.of());
.filter(o -> o.getName() != null && o.getName().equals("Internal"))
.findAny();
if (found.isPresent()) {
return found.get();
}
var internalCollection = DataSourceCollection.createNew("Internal");
internalCollection.setDirectory(getSourcesDir()
.resolve("collections")
.resolve(internalCollection.getUuid().toString()));
this.sourceCollections.add(internalCollection);
return internalCollection;
} }
public synchronized void refreshChildren(DataStoreEntry e) { public synchronized boolean refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
refreshChildren(e, List.of());
}
public synchronized void refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
if (!(e.getStore() instanceof FixedHierarchyStore)) { if (!(e.getStore() instanceof FixedHierarchyStore)) {
return; return false;
} }
try { try {
@ -115,13 +85,15 @@ public abstract class DataStorage {
newChildren.entrySet().stream().filter(entry -> oldChildren.stream().noneMatch(old -> old.getStore().equals(entry.getValue()))).forEach(entry -> { newChildren.entrySet().stream().filter(entry -> oldChildren.stream().noneMatch(old -> old.getStore().equals(entry.getValue()))).forEach(entry -> {
addStoreEntryIfNotPresent(entry.getKey(), entry.getValue()); addStoreEntryIfNotPresent(entry.getKey(), entry.getValue());
}); });
return newChildren.size() > 0;
} catch (Exception ex) { } catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle(); ErrorEvent.fromThrowable(ex).handle();
return false;
} }
} }
public synchronized void deleteChildren(DataStoreEntry e, boolean deep) { public synchronized void deleteChildren(DataStoreEntry e, boolean deep) {
getStoreChildren(e, deep).forEach(entry -> { getLogicalStoreChildren(e, deep).forEach(entry -> {
if (!entry.getConfiguration().isDeletable()) { if (!entry.getConfiguration().isDeletable()) {
return; return;
} }
@ -130,7 +102,7 @@ public abstract class DataStorage {
}); });
} }
public synchronized List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean deep) { public synchronized List<DataStoreEntry> getLogicalStoreChildren(DataStoreEntry entry, boolean deep) {
var children = new ArrayList<>(getStoreEntries().stream() var children = new ArrayList<>(getStoreEntries().stream()
.filter(other -> { .filter(other -> {
if (!other.getState().isUsable()) { if (!other.getState().isUsable()) {
@ -144,75 +116,13 @@ public abstract class DataStorage {
if (deep) { if (deep) {
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) { for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
children.addAll(getStoreChildren(dataStoreEntry, true)); children.addAll(getLogicalStoreChildren(dataStoreEntry, true));
} }
} }
return children; return children;
} }
public synchronized String createUniqueSourceEntryName(DataSourceCollection col, DataSource<?> source) {
var def = source.determineDefaultName();
if (def.isPresent()) {
return createUniqueSourceEntryName(col, def.get());
}
var storeDef = source.getStore().determineDefaultName();
if (storeDef.isPresent()) {
return createUniqueSourceEntryName(col, storeDef.get());
}
var typeName =
switch (source.getType()) {
case TABLE -> AppI18n.get("table");
case STRUCTURE -> AppI18n.get("structure");
case TEXT -> AppI18n.get("text");
case RAW -> AppI18n.get("raw");
case COLLECTION -> AppI18n.get("collection");
};
return createUniqueSourceEntryName(col, typeName);
}
private synchronized String createUniqueStoreEntryName(String base) {
if (DataStorage.get().getStoreEntryIfPresent(base).isEmpty()) {
return base;
}
int counter = 1;
while (true) {
var name = base + counter;
if (DataStorage.get().getStoreEntryIfPresent(name).isEmpty()) {
return name;
}
counter++;
}
}
private synchronized String createUniqueSourceEntryName(DataSourceCollection col, String base) {
base = DataSourceId.cleanString(base);
var id = DataSourceId.create(col != null ? col.getName() : null, base);
if (DataStorage.get().getDataSource(DataSourceReference.id(id)).isEmpty()) {
return base;
}
int counter = 1;
while (true) {
id = DataSourceId.create(col != null ? col.getName() : null, base + counter);
if (DataStorage.get().getDataSource(DataSourceReference.id(id)).isEmpty()) {
return base + counter;
}
counter++;
}
}
public synchronized DataSourceEntry getLatest() {
return latest;
}
public synchronized void setLatest(DataSourceEntry latest) {
this.latest = latest;
}
public abstract Path getInternalStreamPath(@NonNull UUID uuid); public abstract Path getInternalStreamPath(@NonNull UUID uuid);
private void checkImmutable() { private void checkImmutable() {
@ -235,26 +145,6 @@ public abstract class DataStorage {
return dir.resolve("streams"); return dir.resolve("streams");
} }
public synchronized Optional<DataSourceCollection> getCollectionForSourceEntry(DataSourceEntry entry) {
if (entry == null) {
return Optional.of(getInternalCollection());
}
return sourceCollections.stream()
.filter(c -> c.getEntries().contains(entry))
.findAny();
}
public synchronized Optional<DataSourceCollection> getCollectionForName(String name) {
if (name == null) {
return Optional.ofNullable(getInternalCollection());
}
return sourceCollections.stream()
.filter(c -> name.equalsIgnoreCase(c.getName()))
.findAny();
}
public synchronized List<DataStore> getUsableStores() { public synchronized List<DataStore> getUsableStores() {
return new ArrayList<>(getStoreEntries().stream() return new ArrayList<>(getStoreEntries().stream()
.filter(entry -> !entry.isDisabled()) .filter(entry -> !entry.isDisabled())
@ -301,107 +191,18 @@ public abstract class DataStorage {
.findFirst(); .findFirst();
} }
public synchronized Optional<DataSourceEntry> getDataSource(DataSourceReference ref) { public void updateAndRefreshAsync(DataStoreEntry entry, DataStore s) {
Objects.requireNonNull(ref, "ref");
switch (ref.getType()) {
case LATEST -> {
return Optional.ofNullable(latest);
}
case NAME -> {
var found = sourceCollections.stream()
.map(col -> col.getEntries().stream()
.filter(e -> e.getName().equalsIgnoreCase(ref.getName()))
.findAny())
.flatMap(Optional::stream)
.toList();
return Optional.ofNullable(found.size() == 1 ? found.get(0) : null);
}
case ID -> {
var id = ref.getId();
var col = id.getCollectionName() != null
? sourceCollections.stream()
.filter(c ->
c.getName() != null && c.getName().equalsIgnoreCase(id.getCollectionName()))
.findAny()
: Optional.of(getInternalCollection());
if (col.isEmpty()) {
return Optional.empty();
}
return col.get().getEntries().stream()
.filter(e -> e.getName().equalsIgnoreCase(id.getEntryName()))
.findAny();
}
}
throw new AssertionError();
}
public synchronized DataSourceId getId(DataSourceEntry entry) {
Objects.requireNonNull(entry, "entry");
if (!sourceEntries.contains(entry)) {
throw new IllegalArgumentException("Entry not in storage");
}
var col = sourceCollections.stream()
.filter(p -> p.getEntries().contains(entry))
.findAny()
.map(DataSourceCollection::getName)
.orElse(null);
var en = entry.getName();
return DataSourceId.create(col, en);
}
public synchronized void add(DataSourceEntry e, DataSourceCollection c) {
Objects.requireNonNull(e, "entry");
if (c != null && !sourceCollections.contains(c)) {
throw new IllegalArgumentException("Collection does not belong to the storage");
}
if (c == null) {
c = getInternalCollection();
}
var id = DataSourceId.create(c.getName(), e.getName());
if (getDataSource(DataSourceReference.id(id)).isPresent()) {
throw new IllegalArgumentException("Entry with id " + id + " is already in storage");
}
checkImmutable();
// Observe charset of all sources
if (e.getSource() instanceof Charsettable cs) {
AppCharsets.observe(cs.getCharset());
}
TrackEvent.withTrace("storage", "Adding new data source entry")
.tag("name", e.getName())
.tag("uuid", e.getUuid())
.tag("collection", c)
.handle();
e.setDirectory(getSourcesDir().resolve("entries").resolve(e.getUuid().toString()));
this.sourceEntries.add(e);
c.addEntry(e);
save();
latest = e;
}
public void setAndRefreshAsync(DataStoreEntry entry, DataStore s) {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
var old = entry.getStore(); var old = entry.getStore();
var children = getStoreChildren(entry, false); var children = getLogicalStoreChildren(entry, false);
try { try {
entry.setStoreInternal(s); entry.setStoreInternal(s, false);
entry.refresh(true); entry.refresh(true);
// Update old children // Update old children
children.forEach(entry1 -> propagateUpdate(entry1)); children.forEach(entry1 -> propagateUpdate(entry1));
DataStorage.get().refreshChildren(entry, children); DataStorage.get().refreshChildren(entry, children);
} catch (Exception e) { } catch (Exception e) {
entry.setStoreInternal(old); entry.setStoreInternal(old, false);
entry.simpleRefresh(); entry.simpleRefresh();
} }
}); });
@ -420,17 +221,13 @@ public abstract class DataStorage {
} }
void propagateUpdate(DataStoreEntry origin) { void propagateUpdate(DataStoreEntry origin) {
getStoreChildren(origin, false).forEach(entry -> { getLogicalStoreChildren(origin, false).forEach(entry -> {
entry.simpleRefresh(); entry.simpleRefresh();
propagateUpdate(entry); propagateUpdate(entry);
}); });
} }
public void addStoreEntry(@NonNull DataStoreEntry e) { public void addStoreEntry(@NonNull DataStoreEntry e) {
if (getStoreEntryIfPresent(e.getName()).isPresent()) {
throw new IllegalArgumentException("Store with name " + e.getName() + " already exists");
}
synchronized (this) { synchronized (this) {
e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e); this.storeEntries.add(e);
@ -447,22 +244,29 @@ public abstract class DataStorage {
return found.get(); return found.get();
} }
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store); var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
addStoreEntry(e); addStoreEntry(e);
return e; return e;
} }
public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) { public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) {
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store); var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
addStoreEntry(e); addStoreEntry(e);
return e; return e;
} }
public void deleteStoreEntry(@NonNull DataStoreEntry store) { public Optional<String> getStoreDisplayName(DataStore store) {
if (!store.getConfiguration().isDeletable()) { if (store == null) {
// throw new UnsupportedOperationException(); return Optional.empty();
} }
return DataStorage.get().getStoreEntries().stream()
.filter(entry -> !entry.isDisabled() && entry.getStore().equals(store))
.findFirst()
.map(entry -> entry.getName());
}
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
synchronized (this) { synchronized (this) {
this.storeEntries.remove(store); this.storeEntries.remove(store);
} }
@ -475,43 +279,6 @@ public abstract class DataStorage {
this.listeners.add(l); this.listeners.add(l);
} }
public DataSourceCollection createOrGetCollection(String name) {
return getCollectionForName(name).orElseGet(() -> {
var col = DataSourceCollection.createNew(name);
addCollection(col);
return col;
});
}
public synchronized void addCollection(DataSourceCollection c) {
checkImmutable();
c.setDirectory(
getSourcesDir().resolve("collections").resolve(c.getUuid().toString()));
this.sourceCollections.add(c);
this.listeners.forEach(l -> l.onCollectionAdd(c));
}
public synchronized void deleteCollection(DataSourceCollection c) {
checkImmutable();
this.sourceCollections.remove(c);
this.listeners.forEach(l -> l.onCollectionRemove(c));
c.getEntries().forEach(this::deleteSourceEntry);
}
public synchronized void deleteSourceEntry(DataSourceEntry e) {
checkImmutable();
this.sourceEntries.remove(e);
var col = sourceCollections.stream()
.filter(p -> p.getEntries().contains(e))
.findAny();
col.ifPresent(dataSourceCollection -> dataSourceCollection.removeEntry(e));
}
public abstract void load(); public abstract void load();
public void refresh() { public void refresh() {
@ -527,28 +294,6 @@ public abstract class DataStorage {
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
} }
public synchronized Optional<DataSourceEntry> getSourceEntry(UUID id) {
return sourceEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
}
public synchronized Optional<DataSourceEntry> getSourceEntry(DataSource<?> source) {
return sourceEntries.stream()
.filter(e -> e.getSource() != null && e.getSource().equals(source))
.findAny();
}
public synchronized Optional<DataSourceCollection> getCollection(UUID id) {
return sourceCollections.stream().filter(e -> e.getUuid().equals(id)).findAny();
}
public synchronized List<DataSourceEntry> getSourceEntries() {
return new ArrayList<>(sourceEntries);
}
public synchronized List<DataSourceCollection> getSourceCollections() {
return new ArrayList<>(sourceCollections);
}
public synchronized List<DataStoreEntry> getStoreEntries() { public synchronized List<DataStoreEntry> getStoreEntries() {
return new ArrayList<>(storeEntries); return new ArrayList<>(storeEntries);
} }

View file

@ -69,15 +69,6 @@ public class DataStorageParser {
return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds)); return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds));
}); });
node = replaceReferenceIdsForType(node, "sourceId", id -> {
var foundEntry = DataStorage.get().getSourceEntry(id);
if (foundEntry.isPresent()) {
var sourceNode = mapper.valueToTree(foundEntry.get().getSource());
// return Optional.of(resolvesReferenceIds(sourceNode));
}
return Optional.empty();
});
return node; return node;
} }

View file

@ -53,22 +53,6 @@ public class DataStorageWriter {
"storeId", "storeId",
isRoot); isRoot);
node = replaceReferencesWithIds(
node,
possibleReference -> {
try {
var source = mapper.treeToValue(possibleReference, DataSource.class);
if (!isRoot) {
var found = DataStorage.get().getSourceEntry(source);
return found.map(dataSourceEntry -> dataSourceEntry.getUuid());
}
} catch (JsonProcessingException ignored) {
}
return Optional.empty();
},
"sourceId",
isRoot);
return node; return node;
} }

View file

@ -80,7 +80,7 @@ public class DataStoreEntry extends StorageElement {
true, true,
State.LOAD_FAILED, State.LOAD_FAILED,
Configuration.defaultConfiguration(), Configuration.defaultConfiguration(),
true); false);
entry.refresh(false); entry.refresh(false);
return entry; return entry;
} }
@ -203,10 +203,12 @@ public class DataStoreEntry extends StorageElement {
simpleRefresh(); simpleRefresh();
} }
void setStoreInternal(DataStore store) { void setStoreInternal(DataStore store, boolean updateTime) {
this.store = store; this.store = store;
this.storeNode = DataStorageWriter.storeToNode(store); this.storeNode = DataStorageWriter.storeToNode(store);
lastModified = Instant.now(); if (updateTime) {
lastModified = Instant.now();
}
dirty = true; dirty = true;
} }

View file

@ -13,8 +13,6 @@ public class ImpersistentStorage extends DataStorage {
@Override @Override
public void load() { public void load() {
// Add temporary collection it is not added yet
getInternalCollection();
} }
@Override @Override

View file

@ -2,6 +2,7 @@ package io.xpipe.app.storage;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.XPipeSession; import io.xpipe.app.util.XPipeSession;
import lombok.NonNull; import lombok.NonNull;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -17,7 +18,6 @@ import java.util.stream.Stream;
public class StandardStorage extends DataStorage { public class StandardStorage extends DataStorage {
private final List<Path> directoriesToKeep = new ArrayList<>(); private final List<Path> directoriesToKeep = new ArrayList<>();
private DataSourceCollection recovery;
private boolean isNewSession() { private boolean isNewSession() {
return XPipeSession.get().isNewSystemSession(); return XPipeSession.get().isNewSystemSession();
@ -59,70 +59,6 @@ public class StandardStorage extends DataStorage {
} catch (Exception ex) { } catch (Exception ex) {
ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
} }
// Delete leftover directories in entries dir
try (var s = Files.list(entriesDir)) {
s.forEach(file -> {
if (directoriesToKeep.contains(file)) {
return;
}
var name = file.getFileName().toString();
try {
UUID uuid;
try {
uuid = UUID.fromString(name);
} catch (Exception ex) {
FileUtils.forceDelete(file.toFile());
return;
}
var entry = getSourceEntry(uuid);
if (entry.isEmpty()) {
TrackEvent.withTrace("storage", "Deleting leftover entry directory")
.tag("uuid", uuid)
.handle();
FileUtils.forceDelete(file.toFile());
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
});
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
}
// Delete leftover directories in collections dir
try (var s = Files.list(collectionsDir)) {
s.forEach(file -> {
if (directoriesToKeep.contains(file)) {
return;
}
var name = file.getFileName().toString();
try {
UUID uuid;
try {
uuid = UUID.fromString(name);
} catch (Exception ex) {
FileUtils.forceDelete(file.toFile());
return;
}
var col = getCollection(uuid);
if (col.isEmpty()) {
TrackEvent.withTrace("storage", "Deleting leftover collection directory")
.tag("uuid", uuid)
.handle();
FileUtils.forceDelete(file.toFile());
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
});
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
}
} }
public synchronized void load() { public synchronized void load() {
@ -158,75 +94,23 @@ public class StandardStorage extends DataStorage {
storeEntries.add(entry); storeEntries.add(entry);
} catch (Exception e) { } catch (Exception e) {
directoriesToKeep.add(path); // We only keep invalid entries in developer mode as there's no point in keeping them in
// production.
if (AppPrefs.get().developerMode().getValue()) {
directoriesToKeep.add(path);
}
ErrorEvent.fromThrowable(e).omitted(true).build().handle(); ErrorEvent.fromThrowable(e).omitted(true).build().handle();
} }
}); });
// Refresh to update state
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh()); storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh());
}
try (var dirs = Files.list(entriesDir)) { // Remove even incomplete stores when in production
dirs.filter(Files::isDirectory).forEach(path -> { if (!AppPrefs.get().developerMode().getValue()) {
try { storeEntries.removeIf(entry -> {
try (Stream<Path> list = Files.list(path)) { return !entry.getState().isUsable();
if (list.findAny().isEmpty()) { });
return;
}
}
var entry = DataSourceEntry.fromDirectory(path);
if (entry == null) {
return;
}
sourceEntries.add(entry);
} catch (Exception e) {
directoriesToKeep.add(path);
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
});
}
try (var dirs = Files.list(collectionsDir)) {
dirs.filter(Files::isDirectory).forEach(path -> {
try {
try (Stream<Path> list = Files.list(path)) {
if (list.findAny().isEmpty()) {
return;
}
}
var col = DataSourceCollection.fromDirectory(this, path);
// Empty temp on new session
if (col.getName() == null && newSession) {
for (var e : col.getEntries()) {
var file = e.getDirectory();
TrackEvent.withTrace("storage", "Deleting temporary entry directory")
.tag("uuid", e.getUuid())
.handle();
FileUtils.forceDelete(file.toFile());
}
col.clear();
}
sourceCollections.add(col);
} catch (Exception e) {
directoriesToKeep.add(path);
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
});
}
// Add temporary collection it is not added yet
getInternalCollection();
for (var e : getSourceEntries()) {
var inCol = getSourceCollections().stream()
.anyMatch(col -> col.getEntries().contains(e));
if (!inCol) {
recovery = createOrGetCollection("Recovery");
recovery.addEntry(e);
} }
} }
} catch (IOException ex) { } catch (IOException ex) {
@ -257,31 +141,6 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
} }
}); });
// Save entries
sourceEntries.stream()
.filter(dataStoreEntry -> dataStoreEntry.shouldSave())
.forEach(e -> {
try {
e.writeDataToDisk();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
});
// Save collections
for (var c : sourceCollections) {
if (c.equals(recovery)) {
continue;
}
try {
c.writeDataToDisk();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
}
deleteLeftovers(); deleteLeftovers();
} }

View file

@ -5,8 +5,4 @@ public interface StorageListener {
void onStoreAdd(DataStoreEntry entry); void onStoreAdd(DataStoreEntry entry);
void onStoreRemove(DataStoreEntry entry); void onStoreRemove(DataStoreEntry entry);
void onCollectionAdd(DataSourceCollection collection);
void onCollectionRemove(DataSourceCollection collection);
} }

View file

@ -1,6 +1,7 @@
package io.xpipe.app.util; package io.xpipe.app.util;
import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
@ -10,7 +11,7 @@ public class DataStoreFormatter {
public static String formatSubHost(IntFunction<String> func, DataStore at, int length) { public static String formatSubHost(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null) ? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null; : null;
if (atString == null) { if (atString == null) {
return func.apply(length); return func.apply(length);
@ -22,7 +23,7 @@ public class DataStoreFormatter {
public static String formatAtHost(IntFunction<String> func, DataStore at, int length) { public static String formatAtHost(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null) ? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null; : null;
if (atString == null) { if (atString == null) {
return func.apply(length); return func.apply(length);
@ -34,7 +35,7 @@ public class DataStoreFormatter {
public static String formatViaProxy(IntFunction<String> func, DataStore at, int length) { public static String formatViaProxy(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null) ? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null; : null;
if (atString == null) { if (atString == null) {
return func.apply(length); return func.apply(length);
@ -53,7 +54,7 @@ public class DataStoreFormatter {
return "?"; return "?";
} }
var named = XPipeDaemon.getInstance().getStoreName(input); var named = DataStorage.get().getStoreDisplayName(input);
if (named.isPresent()) { if (named.isPresent()) {
return cut(named.get(), length); return cut(named.get(), length);
} }

View file

@ -6,7 +6,6 @@ import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter; import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.impl.LocalStore; import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataFlow; import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
@ -14,8 +13,6 @@ import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import lombok.Value; import lombok.Value;
import java.util.function.Predicate;
public class DialogHelper { public class DialogHelper {
public static Dialog addressQuery(Address address) { public static Dialog addressQuery(Address address) {
@ -26,7 +23,7 @@ public class DialogHelper {
} }
public static Dialog machineQuery(DataStore store) { public static Dialog machineQuery(DataStore store) {
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("localhost"); var storeName = DataStorage.get().getStoreDisplayName(store).orElse("localhost");
return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING) return Dialog.query("Machine", false, true, false, storeName, QueryConverter.STRING)
.map((String name) -> { .map((String name) -> {
if (name.equals("local") || name.equals("localhost")) { if (name.equals("local") || name.equals("localhost")) {
@ -51,7 +48,7 @@ public class DialogHelper {
} }
public static Dialog shellQuery(String displayName, DataStore store) { public static Dialog shellQuery(String displayName, DataStore store) {
var storeName = XPipeDaemon.getInstance().getStoreName(store).orElse("localhost"); var storeName = DataStorage.get().getStoreDisplayName(store).orElse("localhost");
return Dialog.query(displayName, false, true, false, storeName, QueryConverter.STRING) return Dialog.query(displayName, false, true, false, storeName, QueryConverter.STRING)
.map((String name) -> { .map((String name) -> {
if (name.equals("local") || name.equals("localhost")) { if (name.equals("local") || name.equals("localhost")) {
@ -96,7 +93,7 @@ public class DialogHelper {
} }
public static Dialog namedStoreQuery(DataStore store, Class<? extends DataStore> filter) { public static Dialog namedStoreQuery(DataStore store, Class<? extends DataStore> filter) {
var name = XPipeDaemon.getInstance().getStoreName(store).orElse(null); var name = DataStorage.get().getStoreDisplayName(store).orElse(null);
return Dialog.query("Store", false, true, false, name, QueryConverter.STRING) return Dialog.query("Store", false, true, false, name, QueryConverter.STRING)
.map((String newName) -> { .map((String newName) -> {
var found = DataStorage.get() var found = DataStorage.get()
@ -110,18 +107,6 @@ public class DialogHelper {
}); });
} }
public static Dialog sourceQuery(DataSource<?> source, Predicate<DataSource<?>> filter) {
var id = XPipeDaemon.getInstance().getSourceId(source).orElse(null);
return Dialog.query("Source Id", false, true, false, id, QueryConverter.STRING)
.map((String newName) -> {
var found = XPipeDaemon.getInstance().getSource(newName).orElseThrow();
if (!filter.test(found)) {
throw new IllegalArgumentException("Incompatible store type");
}
return found;
});
}
public static Dialog passwordQuery(SecretValue password) { public static Dialog passwordQuery(SecretValue password) {
return Dialog.querySecret("Password", false, true, password); return Dialog.querySecret("Password", false, true, password);
} }

View file

@ -1,24 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import java.util.Optional;
import java.util.ServiceLoader;
public interface XPipeDaemon {
static XPipeDaemon getInstance() {
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
}
static Optional<XPipeDaemon> getInstanceIfPresent() {
return ServiceLoader.load(XPipeDaemon.class).findFirst();
}
Optional<DataSource<?>> getSource(String id);
Optional<String> getStoreName(DataStore store);
Optional<String> getSourceId(DataSource<?> source);
}

View file

@ -1,39 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.DataStore;
import java.util.Optional;
public class XPipeDaemonProvider implements XPipeDaemon {
@Override
public Optional<DataSource<?>> getSource(String id) {
var sourceId = DataSourceId.fromString(id);
return DataStorage.get()
.getDataSource(DataSourceReference.id(sourceId))
.map(dataSourceEntry -> dataSourceEntry.getSource());
}
@Override
public Optional<String> getStoreName(DataStore store) {
if (store == null) {
return Optional.empty();
}
return DataStorage.get().getStoreEntries().stream()
.filter(entry -> !entry.isDisabled() && entry.getStore().equals(store))
.findFirst()
.map(entry -> entry.getName());
}
@Override
public Optional<String> getSourceId(DataSource<?> source) {
var entry = DataStorage.get().getSourceEntry(source);
return entry.map(
dataSourceEntry -> DataStorage.get().getId(dataSourceEntry).toString());
}
}

View file

@ -1,7 +1,6 @@
import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.core.AppLogs; import io.xpipe.app.core.AppLogs;
import io.xpipe.app.exchange.*; import io.xpipe.app.exchange.*;
import io.xpipe.app.exchange.api.*;
import io.xpipe.app.exchange.cli.*; import io.xpipe.app.exchange.cli.*;
import io.xpipe.app.ext.*; import io.xpipe.app.ext.*;
import io.xpipe.app.issue.EventHandler; import io.xpipe.app.issue.EventHandler;
@ -9,8 +8,6 @@ import io.xpipe.app.issue.EventHandlerImpl;
import io.xpipe.app.storage.DataStateProviderImpl; import io.xpipe.app.storage.DataStateProviderImpl;
import io.xpipe.app.util.ProxyManagerProviderImpl; import io.xpipe.app.util.ProxyManagerProviderImpl;
import io.xpipe.app.util.TerminalHelper; import io.xpipe.app.util.TerminalHelper;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.app.util.XPipeDaemonProvider;
import io.xpipe.core.util.DataStateProvider; import io.xpipe.core.util.DataStateProvider;
import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.ModuleLayerLoader;
import io.xpipe.core.util.ProxyFunction; import io.xpipe.core.util.ProxyFunction;
@ -19,18 +16,16 @@ import org.slf4j.spi.SLF4JServiceProvider;
open module io.xpipe.app { open module io.xpipe.app {
exports io.xpipe.app.core; exports io.xpipe.app.core;
exports io.xpipe.app.comp.source;
exports io.xpipe.app.util; exports io.xpipe.app.util;
exports io.xpipe.app; exports io.xpipe.app;
exports io.xpipe.app.issue; exports io.xpipe.app.issue;
exports io.xpipe.app.comp.base; exports io.xpipe.app.comp.base;
exports io.xpipe.app.core.mode; exports io.xpipe.app.core.mode;
exports io.xpipe.app.prefs; exports io.xpipe.app.prefs;
exports io.xpipe.app.comp.source.store; exports io.xpipe.app.comp.store;
exports io.xpipe.app.storage; exports io.xpipe.app.storage;
exports io.xpipe.app.update; exports io.xpipe.app.update;
exports io.xpipe.app.comp.storage; exports io.xpipe.app.comp.storage;
exports io.xpipe.app.comp.storage.collection;
exports io.xpipe.app.ext; exports io.xpipe.app.ext;
exports io.xpipe.app.fxcomps.impl; exports io.xpipe.app.fxcomps.impl;
exports io.xpipe.app.fxcomps; exports io.xpipe.app.fxcomps;
@ -122,7 +117,6 @@ open module io.xpipe.app {
uses EventHandler; uses EventHandler;
uses PrefsProvider; uses PrefsProvider;
uses DataStoreProvider; uses DataStoreProvider;
uses XPipeDaemon;
uses ProxyFunction; uses ProxyFunction;
uses ModuleLayerLoader; uses ModuleLayerLoader;
uses ScanProvider; uses ScanProvider;
@ -142,26 +136,17 @@ open module io.xpipe.app {
AppLogs.Slf4jProvider; AppLogs.Slf4jProvider;
provides EventHandler with provides EventHandler with
EventHandlerImpl; EventHandlerImpl;
provides XPipeDaemon with
XPipeDaemonProvider;
provides MessageExchangeImpl with provides MessageExchangeImpl with
ReadDrainExchangeImpl, ReadDrainExchangeImpl,
ForwardExchangeImpl,
EditStoreExchangeImpl, EditStoreExchangeImpl,
AddSourceExchangeImpl,
StoreProviderListExchangeImpl, StoreProviderListExchangeImpl,
ListCollectionsExchangeImpl,
OpenExchangeImpl, OpenExchangeImpl,
LaunchExchangeImpl, LaunchExchangeImpl,
FocusExchangeImpl, FocusExchangeImpl,
ListEntriesExchangeImpl,
ProxyReadConnectionExchangeImpl, ProxyReadConnectionExchangeImpl,
StatusExchangeImpl, StatusExchangeImpl,
StopExchangeImpl, StopExchangeImpl,
ModeExchangeImpl, ModeExchangeImpl,
WritePreparationExchangeImpl,
WriteExecuteExchangeImpl,
ReadExchangeImpl,
DialogExchangeImpl, DialogExchangeImpl,
ProxyWriteConnectionExchangeImpl, ProxyWriteConnectionExchangeImpl,
RemoveStoreExchangeImpl, RemoveStoreExchangeImpl,
@ -169,22 +154,10 @@ open module io.xpipe.app {
ProxyFunctionExchangeImpl, ProxyFunctionExchangeImpl,
ListStoresExchangeImpl, ListStoresExchangeImpl,
StoreAddExchangeImpl, StoreAddExchangeImpl,
QueryDataSourceExchangeImpl,
RemoveCollectionExchangeImpl,
RemoveEntryExchangeImpl,
RenameCollectionExchangeImpl,
RenameEntryExchangeImpl,
AskpassExchangeImpl, AskpassExchangeImpl,
SourceProviderListExchangeImpl,
QueryStoreExchangeImpl, QueryStoreExchangeImpl,
SelectExchangeImpl,
WriteStreamExchangeImpl, WriteStreamExchangeImpl,
ReadStreamExchangeImpl, ReadStreamExchangeImpl,
QueryTextDataExchangeImpl,
EditExchangeImpl,
QueryTableDataExchangeImpl,
QueryRawDataExchangeImpl,
ConvertExchangeImpl,
InstanceExchangeImpl, InstanceExchangeImpl,
VersionExchangeImpl; VersionExchangeImpl;
} }

Some files were not shown because too many files have changed in this diff Show more