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.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.DataStructureNode;
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.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTableImpl extends DataSourceImpl implements DataTable {
@ -34,9 +25,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
}
public Stream<TupleNode> stream() {
var iterator = new TableIterator();
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
.onClose(iterator::finish);
return Stream.of();
}
@Override
@ -52,71 +41,21 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
@Override
public ArrayNode read(int maxRows) {
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);
}
@Override
public Iterator<TupleNode> iterator() {
return new TableIterator();
}
private class TableIterator implements Iterator<TupleNode> {
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);
return new Iterator<TupleNode>() {
@Override
public boolean hasNext() {
return false;
}
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.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.DataSourceReference;
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.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class DataTextImpl extends DataSourceImpl implements DataText {
@ -53,47 +40,7 @@ public class DataTextImpl extends DataSourceImpl implements DataText {
@Override
public Stream<String> lines() {
var iterator = new Iterator<String>() {
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);
return Stream.of();
}
@Override

View file

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

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -18,6 +19,9 @@ import java.util.function.Function;
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> all;
private final Function<T, Comp<?>> compFunction;
@ -65,6 +69,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
})
.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)) {
listView.getChildren().setAll(newShown);
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;
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.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
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.scene.control.Label;
import javafx.geometry.Pos;
import javafx.scene.layout.*;
import lombok.SneakyThrows;
public class DenseStoreEntryComp extends StoreEntryComp {
private final boolean showIcon;
private final Comp<?> content;
public DenseStoreEntryComp(StoreEntryWrapper entry, boolean showIcon, Comp<?> content) {
super(entry);
super(entry, content);
this.showIcon = showIcon;
this.content = content;
}
protected Region createContent() {
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();
grid.setHgap(10);
if (showIcon) {
var storeIcon = createIcon(30, 25);
grid.getColumnConstraints().add(new ColumnConstraints(45));
grid.getColumnConstraints().add(new ColumnConstraints(30));
grid.add(storeIcon, 0, 0);
GridPane.setHalignment(storeIcon, HPos.CENTER);
} else {
@ -50,60 +31,40 @@ public class DenseStoreEntryComp extends StoreEntryComp {
grid.getColumnConstraints().add(new ColumnConstraints(5));
}
var fill = new ColumnConstraints();
fill.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(new ColumnConstraints(450), fill);
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 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);
var c = content != null ? content.createRegion() : new Region();
grid.add(c, 2, 0);
GridPane.setHalignment(c, HPos.CENTER);
grid.add(createInformation(), 2, 0);
grid.getColumnConstraints().addAll(info, custom);
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);
AppFont.small(size);
AppFont.small(date);
grid.getStyleClass().add("store-entry-grid");
grid.getStyleClass().add("dense");
applyState(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;
return grid;
}
}

View file

@ -1,304 +1,60 @@
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.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.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
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 javafx.scene.layout.*;
import java.util.ArrayList;
public class StandardStoreEntryComp extends StoreEntryComp {
public class StandardStoreEntryComp extends SimpleComp {
public static Comp<?> customSection(StoreEntryWrapper e) {
var prov = e.getEntry().getProvider();
if (prov != null) {
return prov.customDisplay(e);
} else {
return new StandardStoreEntryComp(e);
}
public StandardStoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
super(entry, content);
}
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() {
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();
grid.setHgap(10);
grid.setVgap(0);
var storeIcon = createIcon();
grid.getColumnConstraints()
.addAll(
createShareConstraint(grid, STORE_TYPE_WIDTH), createShareConstraint(grid, NAME_WIDTH),
createShareConstraint(grid, DETAILS_WIDTH), createShareConstraint(grid, BUTTONS_WIDTH));
var storeIcon = createIcon(60, 45);
grid.add(storeIcon, 0, 0, 1, 2);
grid.add(name, 1, 0);
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);
grid.getColumnConstraints().add(new ColumnConstraints(60));
AppFont.small(size);
AppFont.small(date);
grid.add(name, 1, 0);
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");
applyState(grid);
var button = new JFXButton();
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;
return grid;
}
}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.storage.store;
import atlantafx.base.theme.Styles;
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.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;

View file

@ -1,12 +1,17 @@
package io.xpipe.app.comp.storage.store;
import atlantafx.base.controls.Spacer;
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.AppI18n;
import io.xpipe.app.ext.ActionProvider;
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.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
@ -25,6 +30,8 @@ import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
@ -37,22 +44,58 @@ public abstract class StoreEntryComp extends SimpleComp {
if (prov != null) {
return prov.customDisplay(e);
} 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 INCOMPLETE = PseudoClass.getPseudoClass("incomplete");
protected final StoreEntryWrapper entry;
protected final Comp<?> content;
public StoreEntryComp(StoreEntryWrapper entry) {
public StoreEntryComp(StoreEntryWrapper entry, Comp<?> content) {
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() {
var information = new Label();
information.textProperty().bind(PlatformThread.sync(entry.getInformation()));
@ -100,9 +143,6 @@ public abstract class StoreEntryComp extends SimpleComp {
LabelComp name = new LabelComp(Bindings.createStringBinding(
() -> {
return entry.getName()
+ (entry.getInformation().get() != null
? " [" + entry.getInformation().get() + "]"
: "")
+ (filtered.size() > 0 && entry.getEntry().getStore() instanceof FixedHierarchyStore
? " (" + filtered.size() + ")"
: "");
@ -130,6 +170,7 @@ public abstract class StoreEntryComp extends SimpleComp {
entry.getEntry().getProvider().getDisplayName()))
.augment(storeIcon);
}
storeIcon.setPadding(new Insets(3, 0, 0, 0));
return storeIcon;
}
@ -237,7 +278,7 @@ public abstract class StoreEntryComp extends SimpleComp {
});
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.setOnAction(event -> entry.delete());
contextMenu.getItems().add(del);

View file

@ -1,6 +1,5 @@
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.MultiContentComp;
import io.xpipe.app.core.AppState;
@ -23,9 +22,9 @@ public class StoreEntryListComp extends SimpleComp {
.getFilterString()
.map(s -> (storeEntrySection -> storeEntrySection.shouldShow(s))));
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

View file

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

View file

@ -1,6 +1,6 @@
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.ext.ActionProvider;
import io.xpipe.app.fxcomps.util.PlatformThread;
@ -177,7 +177,12 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
entry.updateLastUsed();
found.createAction(entry.getStore().asNeeded()).execute();
} 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(
o -> o.wrapper.getEntry().getState().equals(DataStoreEntry.State.COMPLETE_AND_VALID)
? o.wrapper.getEntry().getLastAccess()
? o.wrapper.getEntry().getLastModified()
: Instant.EPOCH)
.reversed()
.thenComparing(
@ -66,7 +66,7 @@ public class StoreSection implements StorageFilter.Filterable {
.getStore()
.equals(other.getEntry()
.getProvider()
.getLogicalParent(other.getEntry().getStore())));
.getDisplayParent(other.getEntry().getStore())));
var children = BindingsHelper.mappedContentBinding(filtered, entry1 -> create(entry1));
var ordered = BindingsHelper.orderedContentBinding(children, COMPARATOR);
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.issue.ErrorEvent;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.StorageListener;
@ -68,12 +67,6 @@ public class StoreViewState {
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 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.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 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 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.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.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.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.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.VerticalComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.SimpleValidator;
import io.xpipe.app.util.Validatable;
import io.xpipe.app.util.Validator;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataStore;
@ -70,7 +70,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
@Override
protected Region createSimple() {
var isNamedStore =
XPipeDaemon.getInstance().getStoreName(selected.getValue()).isPresent();
DataStorage.get().getStoreDisplayName(selected.getValue()).isPresent();
var localStore = new SimpleObjectProperty<>(
!isNamedStore
&& 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.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.ListViewComp;

View file

@ -1,7 +1,6 @@
package io.xpipe.app.core.mode;
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.core.*;
import io.xpipe.app.issue.*;
@ -55,7 +54,6 @@ public class BaseMode extends OperationMode {
TrackEvent.info("mode", "Background mode shutdown started");
BrowserModel.DEFAULT.reset();
AppSocketServer.reset();
SourceCollectionViewState.reset();
StoreViewState.reset();
DataStorage.reset();
AppPrefs.reset();

View file

@ -1,6 +1,5 @@
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.core.*;
import io.xpipe.app.issue.TrackEvent;
@ -83,7 +82,6 @@ public abstract class PlatformMode extends OperationMode {
UpdateAvailableAlert.showIfNeeded();
}
SourceCollectionViewState.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.DataSourceProviders;
import io.xpipe.app.storage.DataSourceCollection;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
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.impl.NamedStore;
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 lombok.NonNull;
@ -68,42 +63,6 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
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();
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.DataSourceProviders;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.ClientException;
import io.xpipe.beacon.exchange.ReadExchange;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.source.DataSource;
import java.util.UUID;
@ -54,11 +51,6 @@ public class ReadExchangeImpl extends ReadExchange
var entryName =
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
var configRef = DialogExchangeImpl.add(Dialog.chain(typeQ, config), (DataSource<?> s) -> {
var entry = DataSourceEntry.createNew(UUID.randomUUID(), entryName, s);
DataStorage.get().add(entry, DataStorage.get().createOrGetCollection(colName));
});
return Response.builder().config(configRef).build();
return Response.builder().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) {
return new StandardStoreEntryComp(w);
return new StandardStoreEntryComp(w, null);
}
default Comp<?> customContainer(StoreSection section) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.ext;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ModuleLayerLoader;
@ -12,7 +11,8 @@ import io.xpipe.core.util.ProxyFunction;
public class XPipeServiceProviders {
public static void load(ModuleLayer layer) {
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
// TODO
var hasDaemon = true;
ModuleLayerLoader.loadAll(layer, hasDaemon, true, t -> {
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.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LeafShellStore;
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 XPipeDaemon.getInstance().getStoreName(store);
return DataStorage.get().getStoreDisplayName(store);
})
.orElse(AppI18n.get("unknown"));
@ -82,7 +81,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return AppI18n.get("none");
}
return XPipeDaemon.getInstance().getStoreName(store).orElse("?");
return DataStorage.get().getStoreDisplayName(store).orElse("?");
}
@Override

View file

@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.CustomComboBoxBuilder;
import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.FileSystemStore;
import javafx.beans.property.Property;
@ -27,7 +26,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp {
var name = DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(store))
.findAny()
.map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?"))
.map(e -> DataStorage.get().getStoreDisplayName(e).orElse("?"))
.orElse("?");
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;
import io.xpipe.app.core.AppCharsets;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
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.FixedHierarchyStore;
import lombok.Getter;
@ -26,19 +19,13 @@ public abstract class DataStorage {
private static DataStorage INSTANCE;
protected final Path dir;
protected final List<DataSourceEntry> sourceEntries;
protected final List<DataSourceCollection> sourceCollections;
protected final List<DataStoreEntry> storeEntries;
@Getter
private final List<StorageListener> listeners = new ArrayList<>();
private DataSourceEntry latest;
public DataStorage() {
this.dir = AppPrefs.get().storageDirectory().getValue();
this.sourceEntries = new ArrayList<>();
this.sourceCollections = new ArrayList<>();
this.storeEntries = new ArrayList<>();
}
@ -80,30 +67,13 @@ public abstract class DataStorage {
return INSTANCE;
}
public synchronized DataSourceCollection getInternalCollection() {
var found = sourceCollections.stream()
.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 boolean refreshChildren(DataStoreEntry e) {
return refreshChildren(e, List.of());
}
public synchronized void refreshChildren(DataStoreEntry e) {
refreshChildren(e, List.of());
}
public synchronized void refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
public synchronized boolean refreshChildren(DataStoreEntry e, List<DataStoreEntry> oldChildren) {
if (!(e.getStore() instanceof FixedHierarchyStore)) {
return;
return false;
}
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 -> {
addStoreEntryIfNotPresent(entry.getKey(), entry.getValue());
});
return newChildren.size() > 0;
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
return false;
}
}
public synchronized void deleteChildren(DataStoreEntry e, boolean deep) {
getStoreChildren(e, deep).forEach(entry -> {
getLogicalStoreChildren(e, deep).forEach(entry -> {
if (!entry.getConfiguration().isDeletable()) {
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()
.filter(other -> {
if (!other.getState().isUsable()) {
@ -144,75 +116,13 @@ public abstract class DataStorage {
if (deep) {
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
children.addAll(getStoreChildren(dataStoreEntry, true));
children.addAll(getLogicalStoreChildren(dataStoreEntry, true));
}
}
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);
private void checkImmutable() {
@ -235,26 +145,6 @@ public abstract class DataStorage {
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() {
return new ArrayList<>(getStoreEntries().stream()
.filter(entry -> !entry.isDisabled())
@ -301,107 +191,18 @@ public abstract class DataStorage {
.findFirst();
}
public synchronized Optional<DataSourceEntry> getDataSource(DataSourceReference ref) {
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) {
public void updateAndRefreshAsync(DataStoreEntry entry, DataStore s) {
ThreadHelper.runAsync(() -> {
var old = entry.getStore();
var children = getStoreChildren(entry, false);
var children = getLogicalStoreChildren(entry, false);
try {
entry.setStoreInternal(s);
entry.setStoreInternal(s, false);
entry.refresh(true);
// Update old children
children.forEach(entry1 -> propagateUpdate(entry1));
DataStorage.get().refreshChildren(entry, children);
} catch (Exception e) {
entry.setStoreInternal(old);
entry.setStoreInternal(old, false);
entry.simpleRefresh();
}
});
@ -420,17 +221,13 @@ public abstract class DataStorage {
}
void propagateUpdate(DataStoreEntry origin) {
getStoreChildren(origin, false).forEach(entry -> {
getLogicalStoreChildren(origin, false).forEach(entry -> {
entry.simpleRefresh();
propagateUpdate(entry);
});
}
public void addStoreEntry(@NonNull DataStoreEntry e) {
if (getStoreEntryIfPresent(e.getName()).isPresent()) {
throw new IllegalArgumentException("Store with name " + e.getName() + " already exists");
}
synchronized (this) {
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
@ -447,22 +244,29 @@ public abstract class DataStorage {
return found.get();
}
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
var e = DataStoreEntry.createNew(UUID.randomUUID(), name, store);
addStoreEntry(e);
return e;
}
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);
return e;
}
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
if (!store.getConfiguration().isDeletable()) {
// throw new UnsupportedOperationException();
public Optional<String> getStoreDisplayName(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());
}
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
synchronized (this) {
this.storeEntries.remove(store);
}
@ -475,43 +279,6 @@ public abstract class DataStorage {
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 void refresh() {
@ -527,28 +294,6 @@ public abstract class DataStorage {
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() {
return new ArrayList<>(storeEntries);
}

View file

@ -69,15 +69,6 @@ public class DataStorageParser {
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;
}

View file

@ -53,22 +53,6 @@ public class DataStorageWriter {
"storeId",
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;
}

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package io.xpipe.app.storage;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.XPipeSession;
import lombok.NonNull;
import org.apache.commons.io.FileUtils;
@ -17,7 +18,6 @@ import java.util.stream.Stream;
public class StandardStorage extends DataStorage {
private final List<Path> directoriesToKeep = new ArrayList<>();
private DataSourceCollection recovery;
private boolean isNewSession() {
return XPipeSession.get().isNewSystemSession();
@ -59,70 +59,6 @@ public class StandardStorage extends DataStorage {
} catch (Exception ex) {
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() {
@ -158,75 +94,23 @@ public class StandardStorage extends DataStorage {
storeEntries.add(entry);
} 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();
}
});
// Refresh to update state
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh());
}
try (var dirs = Files.list(entriesDir)) {
dirs.filter(Files::isDirectory).forEach(path -> {
try {
try (Stream<Path> list = Files.list(path)) {
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);
// Remove even incomplete stores when in production
if (!AppPrefs.get().developerMode().getValue()) {
storeEntries.removeIf(entry -> {
return !entry.getState().isUsable();
});
}
}
} catch (IOException ex) {
@ -257,31 +141,6 @@ public class StandardStorage extends DataStorage {
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();
}

View file

@ -5,8 +5,4 @@ public interface StorageListener {
void onStoreAdd(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;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore;
@ -10,7 +11,7 @@ public class DataStoreFormatter {
public static String formatSubHost(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null;
if (atString == null) {
return func.apply(length);
@ -22,7 +23,7 @@ public class DataStoreFormatter {
public static String formatAtHost(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null;
if (atString == null) {
return func.apply(length);
@ -34,7 +35,7 @@ public class DataStoreFormatter {
public static String formatViaProxy(IntFunction<String> func, DataStore at, int length) {
var atString = at instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore)
? XPipeDaemon.getInstance().getStoreName(at).orElse(null)
? DataStorage.get().getStoreDisplayName(at).orElse(null)
: null;
if (atString == null) {
return func.apply(length);
@ -53,7 +54,7 @@ public class DataStoreFormatter {
return "?";
}
var named = XPipeDaemon.getInstance().getStoreName(input);
var named = DataStorage.get().getStoreDisplayName(input);
if (named.isPresent()) {
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.QueryConverter;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataFlow;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FileSystem;
@ -14,8 +13,6 @@ import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.SecretValue;
import lombok.Value;
import java.util.function.Predicate;
public class DialogHelper {
public static Dialog addressQuery(Address address) {
@ -26,7 +23,7 @@ public class DialogHelper {
}
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)
.map((String name) -> {
if (name.equals("local") || name.equals("localhost")) {
@ -51,7 +48,7 @@ public class DialogHelper {
}
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)
.map((String name) -> {
if (name.equals("local") || name.equals("localhost")) {
@ -96,7 +93,7 @@ public class DialogHelper {
}
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)
.map((String newName) -> {
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) {
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.core.AppLogs;
import io.xpipe.app.exchange.*;
import io.xpipe.app.exchange.api.*;
import io.xpipe.app.exchange.cli.*;
import io.xpipe.app.ext.*;
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.util.ProxyManagerProviderImpl;
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.ModuleLayerLoader;
import io.xpipe.core.util.ProxyFunction;
@ -19,18 +16,16 @@ import org.slf4j.spi.SLF4JServiceProvider;
open module io.xpipe.app {
exports io.xpipe.app.core;
exports io.xpipe.app.comp.source;
exports io.xpipe.app.util;
exports io.xpipe.app;
exports io.xpipe.app.issue;
exports io.xpipe.app.comp.base;
exports io.xpipe.app.core.mode;
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.update;
exports io.xpipe.app.comp.storage;
exports io.xpipe.app.comp.storage.collection;
exports io.xpipe.app.ext;
exports io.xpipe.app.fxcomps.impl;
exports io.xpipe.app.fxcomps;
@ -122,7 +117,6 @@ open module io.xpipe.app {
uses EventHandler;
uses PrefsProvider;
uses DataStoreProvider;
uses XPipeDaemon;
uses ProxyFunction;
uses ModuleLayerLoader;
uses ScanProvider;
@ -142,26 +136,17 @@ open module io.xpipe.app {
AppLogs.Slf4jProvider;
provides EventHandler with
EventHandlerImpl;
provides XPipeDaemon with
XPipeDaemonProvider;
provides MessageExchangeImpl with
ReadDrainExchangeImpl,
ForwardExchangeImpl,
EditStoreExchangeImpl,
AddSourceExchangeImpl,
StoreProviderListExchangeImpl,
ListCollectionsExchangeImpl,
OpenExchangeImpl,
LaunchExchangeImpl,
FocusExchangeImpl,
ListEntriesExchangeImpl,
ProxyReadConnectionExchangeImpl,
StatusExchangeImpl,
StopExchangeImpl,
ModeExchangeImpl,
WritePreparationExchangeImpl,
WriteExecuteExchangeImpl,
ReadExchangeImpl,
DialogExchangeImpl,
ProxyWriteConnectionExchangeImpl,
RemoveStoreExchangeImpl,
@ -169,22 +154,10 @@ open module io.xpipe.app {
ProxyFunctionExchangeImpl,
ListStoresExchangeImpl,
StoreAddExchangeImpl,
QueryDataSourceExchangeImpl,
RemoveCollectionExchangeImpl,
RemoveEntryExchangeImpl,
RenameCollectionExchangeImpl,
RenameEntryExchangeImpl,
AskpassExchangeImpl,
SourceProviderListExchangeImpl,
QueryStoreExchangeImpl,
SelectExchangeImpl,
WriteStreamExchangeImpl,
ReadStreamExchangeImpl,
QueryTextDataExchangeImpl,
EditExchangeImpl,
QueryTableDataExchangeImpl,
QueryRawDataExchangeImpl,
ConvertExchangeImpl,
InstanceExchangeImpl,
VersionExchangeImpl;
}

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