diff --git a/app/src/main/java/io/xpipe/app/browser/BookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BookmarkList.java index 4c179706..daff2e68 100644 --- a/app/src/main/java/io/xpipe/app/browser/BookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BookmarkList.java @@ -22,7 +22,7 @@ final class BookmarkList extends SimpleComp { @Override protected Region createSimple() { - var list = DataStorage.get().getStores().stream().filter(entry -> entry.getStore() instanceof ShellStore).map(entry -> new Bookmark(entry)).toList(); + var list = DataStorage.get().getStoreEntries().stream().filter(entry -> entry.getStore() instanceof ShellStore).map(entry -> new Bookmark(entry)).toList(); return new ListBoxViewComp<>(FXCollections.observableList(list), FXCollections.observableList(list), bookmark -> { var imgView = new PrettyImageComp(new SimpleStringProperty(bookmark.entry().getProvider().getDisplayIconFileName()), 16, 16).createRegion(); diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java index b7bd6516..823fe157 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java @@ -184,8 +184,7 @@ public class FileBrowserComp extends SimpleComp { () -> { return model.getStore().getValue() != null ? DataStorage.get() - .getEntryByStore(model.getStore().getValue()) - .orElseThrow() + .getStoreEntry(model.getStore().getValue()) .getName() : null; }, @@ -194,8 +193,7 @@ public class FileBrowserComp extends SimpleComp { () -> { return model.getStore().getValue() != null ? DataStorage.get() - .getEntryByStore(model.getStore().getValue()) - .orElseThrow() + .getStoreEntry(model.getStore().getValue()) .getProvider() .getDisplayIconFileName() : null; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index 4c11b875..0dd5e8f4 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -55,7 +56,7 @@ final class OpenFileSystemModel { public Optional cd(String path) { var newPath = FileSystemHelper.normalizeDirectoryPath(this, path); - if (!path.equals(newPath)) { + if (!Objects.equals(path, newPath)) { return Optional.of(newPath); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java index 363860ae..2c270f13 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java @@ -1,6 +1,6 @@ package io.xpipe.app.comp.base; -import com.jfoenix.controls.JFXSpinner; +import atlantafx.base.controls.RingProgressIndicator; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure; @@ -25,13 +25,16 @@ public class LoadingOverlayComp extends Comp> { public CompStructure createBase() { var compStruc = comp.createStructure(); - JFXSpinner loading = new JFXSpinner(); - loading.getStyleClass().add("spinner"); + var loading = new RingProgressIndicator(0, false); + loading.setProgress(-1); + loading.setPrefWidth(50); + loading.setPrefHeight(50); + var loadingBg = new StackPane(loading); loadingBg.getStyleClass().add("loading-comp"); loadingBg.setVisible(showLoading.getValue()); - ; + var listener = new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean busy) { @@ -64,7 +67,7 @@ public class LoadingOverlayComp extends Comp> { } } }; - showLoading.addListener(listener); + PlatformThread.sync(showLoading).addListener(listener); var stack = new StackPane(compStruc.get(), loadingBg); return new SimpleCompStructure<>(stack); diff --git a/app/src/main/java/io/xpipe/app/comp/source/GuiDsCreatorTransferStep.java b/app/src/main/java/io/xpipe/app/comp/source/GuiDsCreatorTransferStep.java index 534c4428..10256d7d 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/GuiDsCreatorTransferStep.java +++ b/app/src/main/java/io/xpipe/app/comp/source/GuiDsCreatorTransferStep.java @@ -82,7 +82,7 @@ public class GuiDsCreatorTransferStep extends MultiStepComp.Step(shown, list, prop, (T s) -> { - var e = DataStorage.get().getEntryBySource(s).orElseThrow(); + 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()); diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java index b23e9637..26791f86 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java @@ -91,7 +91,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { show(e.getName(), e.getProvider(), e.getStore(), v -> true, newE -> { ThreadHelper.runAsync(() -> { e.applyChanges(newE); - if (!DataStorage.get().getStores().contains(e)) { + if (!DataStorage.get().getStoreEntries().contains(e)) { DataStorage.get().addStoreEntry(e); ScanAlert.showIfNeeded(e.getStore()); } diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java index 08c58107..1f03672f 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/NamedStoreChoiceComp.java @@ -72,7 +72,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable { return false; } - var e = DataStorage.get().getStore(store); + var e = DataStorage.get().getStoreEntry(store); return filter.getValue().test(e); }; }, @@ -116,8 +116,8 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable { refreshShown(list, shown); }); - var prop = new SimpleObjectProperty<>( - DataStorage.get().getEntryByStore(selected.getValue()).orElse(null)); + var prop = new SimpleObjectProperty<>(selected.getValue() != null ? + DataStorage.get().getStoreEntryIfPresent(selected.getValue()).orElse(null):null); setUpListener(prop); var filterComp = new FilterComp(filterString) diff --git a/app/src/main/java/io/xpipe/app/comp/storage/collection/SourceCollectionWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/collection/SourceCollectionWrapper.java index 93819e13..f4ac7011 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/collection/SourceCollectionWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/collection/SourceCollectionWrapper.java @@ -73,7 +73,7 @@ public class SourceCollectionWrapper implements StorageFilter.Filterable { public void clean() { var entries = List.copyOf(collection.getEntries()); - entries.forEach(e -> DataStorage.get().deleteEntry(e)); + entries.forEach(e -> DataStorage.get().deleteSourceEntry(e)); } private void setupListeners() { diff --git a/app/src/main/java/io/xpipe/app/comp/storage/source/SourceEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/source/SourceEntryWrapper.java index 11e622ef..e9daa800 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/source/SourceEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/source/SourceEntryWrapper.java @@ -72,7 +72,7 @@ public class SourceEntryWrapper implements StorageFilter.Filterable { return; } - DataStorage.get().deleteEntry(entry); + DataStorage.get().deleteSourceEntry(entry); } private > void update() { diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java index 5ab8c253..20eb5ed4 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryListComp.java @@ -35,7 +35,7 @@ public class StoreEntryListComp extends SimpleComp { BindingsHelper.persist( Bindings.not(Bindings.isEmpty(StoreViewState.get().getShownEntries())))); - map.put(new StoreStorageEmptyIntroComp(), StoreViewState.get().emptyProperty()); + map.put(new StoreIntroComp(), StoreViewState.get().emptyProperty()); map.put( new StoreNotFoundComp(), BindingsHelper.persist(Bindings.and( diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java index 9bdb2708..0e2ceef9 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java @@ -11,7 +11,6 @@ import io.xpipe.app.storage.DataStoreEntry; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.*; -import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; import lombok.Getter; @@ -31,7 +30,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { private final Property state = new SimpleObjectProperty<>(); private final StringProperty information = new SimpleStringProperty(); private final StringProperty summary = new SimpleStringProperty(); - private final Map actionProviders; + private final Map actionProviders; private final ObservableValue defaultActionProvider; private final BooleanProperty editable = new SimpleBooleanProperty(); private final BooleanProperty renamable = new SimpleBooleanProperty(); @@ -53,33 +52,26 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { .isAssignableFrom(entry.getStore().getClass()); }) .forEach(dataStoreActionProvider -> { - var property = Bindings.createBooleanBinding( - () -> { - if (!entry.getState().isUsable()) { - return false; - } - - return dataStoreActionProvider - .getDataStoreCallSite() - .isApplicable(entry.getStore().asNeeded()); - }, - disabledProperty(), - state, - lastAccess); - actionProviders.put(dataStoreActionProvider, property); + actionProviders.put(dataStoreActionProvider, new SimpleBooleanProperty(true)); }); - this.defaultActionProvider = Bindings.createObjectBinding(() -> { - var found = actionProviders.entrySet().stream() - .filter(e -> e.getValue().get()) - .filter(e -> e.getKey().getDataStoreCallSite() != null - && e.getKey().getDataStoreCallSite().isDefault()) - .findFirst(); - return found.map(p -> p.getKey()).orElse(null); - }, actionProviders.values().toArray(Observable[]::new)); + this.defaultActionProvider = Bindings.createObjectBinding( + () -> { + var found = actionProviders.entrySet().stream() + .filter(e -> e.getValue().get()) + .filter(e -> e.getKey().getDataStoreCallSite() != null + && e.getKey().getDataStoreCallSite().isDefault()) + .findFirst(); + return found.map(p -> p.getKey()).orElse(null); + }, + actionProviders.values().toArray(Observable[]::new)); setupListeners(); update(); } + public boolean isInStorage() { + return DataStorage.get().getStoreEntries().contains(entry); + } + public void editDialog() { GuiDsStoreCreator.showEdit(entry); } @@ -130,6 +122,33 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { || AppPrefs.get().developerDisableGuiRestrictions().getValue()); deletable.setValue(entry.getConfiguration().isDeletable() || AppPrefs.get().developerDisableGuiRestrictions().getValue()); + + actionProviders.keySet().forEach(dataStoreActionProvider -> { + if (!isInStorage()) { + actionProviders.get(dataStoreActionProvider).set(false); + return; + } + + if (!entry.getState().isUsable() + && !dataStoreActionProvider + .getDataStoreCallSite() + .activeType() + .equals(ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_ENABLE)) { + actionProviders.get(dataStoreActionProvider).set(false); + return; + } + + try { + actionProviders + .get(dataStoreActionProvider) + .set(dataStoreActionProvider + .getDataStoreCallSite() + .isApplicable(entry.getStore().asNeeded())); + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).handle(); + actionProviders.get(dataStoreActionProvider).set(false); + } + }); } @Override diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreStorageEmptyIntroComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java similarity index 68% rename from app/src/main/java/io/xpipe/app/comp/storage/store/StoreStorageEmptyIntroComp.java rename to app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java index 013b4f43..9461f56c 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreStorageEmptyIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java @@ -4,8 +4,11 @@ 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 io.xpipe.app.util.ScanAlert; +import io.xpipe.core.impl.LocalStore; import javafx.geometry.Orientation; import javafx.geometry.Pos; +import javafx.scene.control.Button; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.Separator; @@ -14,7 +17,8 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.kordamp.ikonli.javafx.FontIcon; -public class StoreStorageEmptyIntroComp extends SimpleComp { + +public class StoreIntroComp extends SimpleComp { @Override public Region createSimple() { @@ -22,29 +26,18 @@ public class StoreStorageEmptyIntroComp extends SimpleComp { AppFont.setSize(title, 7); title.getStyleClass().add("title-header"); - var descFi = new FontIcon("mdi2i-information-outline"); var introDesc = new Label(AppI18n.get("storeIntroDescription")); - introDesc.heightProperty().addListener((c, o, n) -> { - descFi.iconSizeProperty().set(n.intValue()); - }); - var mfi = new FontIcon("mdi2h-home-plus-outline"); + var mfi = new FontIcon("mdi2m-magnify"); var machine = new Label(AppI18n.get("storeMachineDescription"), mfi); machine.heightProperty().addListener((c, o, n) -> { mfi.iconSizeProperty().set(n.intValue()); }); - var dfi = new FontIcon("mdi2d-database-plus-outline"); - var database = new Label(AppI18n.get("storeDatabaseDescription"), dfi); - database.heightProperty().addListener((c, o, n) -> { - dfi.iconSizeProperty().set(n.intValue()); - }); - - var fi = new FontIcon("mdi2c-card-plus-outline"); - var stream = new Label(AppI18n.get("storeStreamDescription"), fi); - stream.heightProperty().addListener((c, o, n) -> { - fi.iconSizeProperty().set(n.intValue()); - }); + var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify")); + scanButton.setOnAction(event -> ScanAlert.showIfNeeded(new LocalStore())); + var scanPane = new StackPane(scanButton); + scanPane.setAlignment(Pos.CENTER); var dofi = new FontIcon("mdi2b-book-open-variant"); var documentation = new Label(AppI18n.get("introDocumentation"), dofi); @@ -63,10 +56,7 @@ public class StoreStorageEmptyIntroComp extends SimpleComp { introDesc, new Separator(Orientation.HORIZONTAL), machine, - new Separator(Orientation.HORIZONTAL), - database, - new Separator(Orientation.HORIZONTAL), - stream, + scanPane, new Separator(Orientation.HORIZONTAL), documentation, docLinkPane); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java index 6d4ff20f..00c8eac8 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreViewState.java @@ -54,7 +54,7 @@ public class StoreViewState { } private void addStorageGroupListeners() { - allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStores().stream() + allEntries.setAll(FXCollections.observableArrayList(DataStorage.get().getStoreEntries().stream() .map(StoreEntryWrapper::new) .toList())); diff --git a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java index 5a6d7f0d..f7047d6b 100644 --- a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java @@ -46,7 +46,7 @@ public interface MessageExchangeImpl !entry.isDisabled() && entry.getProvider().shouldShow()) .sorted(Comparator.comparing(dataStoreEntry -> dataStoreEntry.getLastUsed())) .map(col -> StoreListEntry.builder() diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java index b217e0f7..5fd66a81 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/ReadDrainExchangeImpl.java @@ -18,9 +18,9 @@ public class ReadDrainExchangeImpl extends ReadDrainExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { - var ds = DataStorage.get().getStoreIfPresent(msg.getName()); + var ds = DataStorage.get().getStoreEntryIfPresent(msg.getName()); if (ds.isEmpty()) { - ds = Optional.of(DataStorage.get().addStore(msg.getName(), msg.getStore())); + ds = Optional.of(DataStorage.get().addStoreEntry(msg.getName(), msg.getStore())); } if (!(ds.get().getStore() instanceof SinkDrainStore)) { diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveEntryExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveEntryExchangeImpl.java index a4c04bf3..36b98156 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveEntryExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveEntryExchangeImpl.java @@ -12,7 +12,7 @@ public class RemoveEntryExchangeImpl extends RemoveEntryExchange public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { var e = getSourceEntry(msg.getRef(), null, true); var id = DataStorage.get().getId(e); - DataStorage.get().deleteEntry(e); + DataStorage.get().deleteSourceEntry(e); return Response.builder().id(id).build(); } } diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java index 0ec2ce1b..40997a35 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RemoveStoreExchangeImpl.java @@ -11,7 +11,7 @@ public class RemoveStoreExchangeImpl extends RemoveStoreExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { - var s = DataStorage.get().getStore(msg.getStoreName(), true); + var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true); if (!s.getConfiguration().isDeletable()) { throw new ClientException("Store is not deletable"); } diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RenameEntryExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RenameEntryExchangeImpl.java index ca679069..b0bcddf9 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RenameEntryExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RenameEntryExchangeImpl.java @@ -11,7 +11,7 @@ public class RenameEntryExchangeImpl extends RenameEntryExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { var e = getSourceEntry(msg.getRef(), null, true); - DataStorage.get().deleteEntry(e); + DataStorage.get().deleteSourceEntry(e); DataStorage.get() .add( e, diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java index 49f4da10..7fdbb3c8 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/RenameStoreExchangeImpl.java @@ -10,8 +10,8 @@ public class RenameStoreExchangeImpl extends RenameStoreExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { - var s = DataStorage.get().getStore(msg.getStoreName(), true); - DataStorage.get().renameStore(s, msg.getNewName()); + var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true); + DataStorage.get().renameStoreEntry(s, msg.getNewName()); return Response.builder().build(); } } diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java index 580ba5d4..d4b71c04 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/StoreAddExchangeImpl.java @@ -51,7 +51,7 @@ public class StoreAddExchangeImpl extends StoreAddExchange return; } - DataStorage.get().addStore(name.getValue(), store); + DataStorage.get().addStoreEntry(name.getValue(), store); }); return StoreAddExchange.Response.builder().config(config).build(); @@ -81,7 +81,7 @@ public class StoreAddExchangeImpl extends StoreAddExchange var nameQ = Dialog.retryIf( Dialog.query("Store name", true, true, false, name.getValue(), QueryConverter.STRING), (String r) -> { - return DataStorage.get().getStoreIfPresent(r).isPresent() + return DataStorage.get().getStoreEntryIfPresent(r).isPresent() ? "Store with name " + r + " already exists" : null; }) diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java index dc747afc..e28f5d14 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/FileSystemStoreChoiceComp.java @@ -2,6 +2,7 @@ package io.xpipe.app.fxcomps.impl; import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.CustomComboBoxBuilder; import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.impl.FileStore; @@ -23,7 +24,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp { } private static String getName(FileSystemStore store) { - var name = XPipeDaemon.getInstance().getNamedStores().stream() + var name = DataStorage.get().getUsableStores().stream() .filter(e -> e.equals(store)) .findAny() .map(e -> XPipeDaemon.getInstance().getStoreName(e).orElse("?")) @@ -61,7 +62,7 @@ public class FileSystemStoreChoiceComp extends SimpleComp { var comboBox = new CustomComboBoxBuilder(fileSystemProperty, this::createGraphic, null, v -> true); comboBox.setSelectedDisplay(this::createDisplayGraphic); - XPipeDaemon.getInstance().getNamedStores().stream() + DataStorage.get().getUsableStores().stream() .filter(e -> e instanceof FileSystemStore) .map(e -> (FileSystemStore) e) .forEach(comboBox::add); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ShellStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ShellStoreChoiceComp.java index 35250a28..9af9f5ce 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ShellStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ShellStoreChoiceComp.java @@ -3,6 +3,7 @@ package io.xpipe.app.fxcomps.impl; import io.xpipe.app.core.AppI18n; 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.ShellStore; @@ -52,7 +53,7 @@ public class ShellStoreChoiceComp extends SimpleComp { var imgView = new PrettyImageComp(new SimpleStringProperty(provider.getDisplayIconFileName()), 16, 16).createRegion(); - var name = XPipeDaemon.getInstance().getNamedStores().stream() + var name = DataStorage.get().getUsableStores().stream() .filter(e -> e.equals(s)) .findAny() .flatMap(store -> { @@ -74,7 +75,7 @@ public class ShellStoreChoiceComp extends SimpleComp { new CustomComboBoxBuilder(selected, this::createGraphic, new Label(AppI18n.get("none")), n -> true); comboBox.setUnknownNode(t -> createGraphic(t)); - var available = XPipeDaemon.getInstance().getNamedStores().stream() + var available = DataStorage.get().getUsableStores().stream() .filter(s -> s != self) .filter(s -> storeClass.isAssignableFrom(s.getClass()) && applicableCheck.test((T) s)) .map(s -> (ShellStore) s) diff --git a/app/src/main/java/io/xpipe/app/storage/DataSourceCollection.java b/app/src/main/java/io/xpipe/app/storage/DataSourceCollection.java index e41b6c50..1d49875c 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataSourceCollection.java +++ b/app/src/main/java/io/xpipe/app/storage/DataSourceCollection.java @@ -66,7 +66,7 @@ public class DataSourceCollection extends StorageElement { JavaType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, UUID.class); var entries = new LinkedHashMap(); for (var u : mapper.>readValue(dir.resolve("entries.json").toFile(), listType)) { - var v = storage.getSourceEntryByUuid(u).orElse(null); + var v = storage.getSourceEntry(u).orElse(null); entries.put(u, v); } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java index f50b2446..d0c73c39 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java @@ -15,7 +15,7 @@ public class DataStateProviderImpl extends DataStateProvider { return; } - var entry = DataStorage.get().getEntryByStore(store); + var entry = DataStorage.get().getStoreEntryIfPresent(store); if (entry.isEmpty()) { return; } @@ -30,7 +30,7 @@ public class DataStateProviderImpl extends DataStateProvider { return def.get(); } - var entry = DataStorage.get().getEntryByStore(store); + var entry = DataStorage.get().getStoreEntryIfPresent(store); if (entry.isEmpty()) { return def.get(); } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 8230d3f6..5f1f4ea2 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -79,7 +79,7 @@ public abstract class DataStorage { return INSTANCE; } - public DataSourceCollection getInternalCollection() { + public synchronized DataSourceCollection getInternalCollection() { var found = sourceCollections.stream() .filter(o -> o.getName() != null && o.getName().equals("Internal")) .findAny(); @@ -95,7 +95,28 @@ public abstract class DataStorage { return internalCollection; } - public String createUniqueSourceEntryName(DataSourceCollection col, DataSource source) { + public synchronized List getStoreChildren(DataStoreEntry entry, boolean deep) { + var children = new ArrayList<>(getStoreEntries().stream().filter(other -> { + if (!other.getState().isUsable()) { + return false; + } + + var parent = other + .getProvider() + .getParent(other.getStore()); + return entry.getStore().equals(parent); + }).toList()); + + if (deep) { + for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) { + children.addAll(getStoreChildren(dataStoreEntry, true)); + } + } + + return children; + } + + public synchronized String createUniqueSourceEntryName(DataSourceCollection col, DataSource source) { var def = source.determineDefaultName(); if (def.isPresent()) { return createUniqueSourceEntryName(col, def.get()); @@ -117,22 +138,22 @@ public abstract class DataStorage { return createUniqueSourceEntryName(col, typeName); } - private String createUniqueStoreEntryName(String base) { - if (DataStorage.get().getStoreIfPresent(base).isEmpty()) { + 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().getStoreIfPresent(name).isEmpty()) { + if (DataStorage.get().getStoreEntryIfPresent(name).isEmpty()) { return name; } counter++; } } - private String createUniqueSourceEntryName(DataSourceCollection col, String base) { + 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()) { @@ -149,11 +170,11 @@ public abstract class DataStorage { } } - public DataSourceEntry getLatest() { + public synchronized DataSourceEntry getLatest() { return latest; } - public void setLatest(DataSourceEntry latest) { + public synchronized void setLatest(DataSourceEntry latest) { this.latest = latest; } @@ -179,7 +200,7 @@ public abstract class DataStorage { return dir.resolve("streams"); } - public Optional getCollectionForSourceEntry(DataSourceEntry entry) { + public synchronized Optional getCollectionForSourceEntry(DataSourceEntry entry) { if (entry == null) { return Optional.of(getInternalCollection()); } @@ -189,7 +210,7 @@ public abstract class DataStorage { .findAny(); } - public Optional getCollectionForName(String name) { + public synchronized Optional getCollectionForName(String name) { if (name == null) { return Optional.ofNullable(getInternalCollection()); } @@ -199,21 +220,22 @@ public abstract class DataStorage { .findAny(); } - public Optional getStoreIfPresent(@NonNull String name) { - return storeEntries.stream() - .filter(n -> n.getName().equalsIgnoreCase(name)) - .findFirst(); + public synchronized List getUsableStores() { + return new ArrayList<>(getStoreEntries().stream() + .filter(entry -> !entry.isDisabled()) + .map(DataStoreEntry::getStore) + .toList()); } - public void renameStore(DataStoreEntry entry, String name) { - if (getStoreIfPresent(name).isPresent()) { + public synchronized void renameStoreEntry(DataStoreEntry entry, String name) { + if (getStoreEntryIfPresent(name).isPresent()) { throw new IllegalArgumentException("Store with name " + name + " already exists"); } entry.setName(name); } - public DataStoreEntry getStore(@NonNull String name, boolean acceptDisabled) { + public synchronized DataStoreEntry getStoreEntry(@NonNull String name, boolean acceptDisabled) { var entry = storeEntries.stream() .filter(n -> n.getName().equalsIgnoreCase(name)) .findFirst() @@ -224,7 +246,7 @@ public abstract class DataStorage { return entry; } - public DataStoreEntry getStore(@NonNull DataStore store) { + public synchronized DataStoreEntry getStoreEntry(@NonNull DataStore store) { var entry = storeEntries.stream() .filter(n -> store.equals(n.getStore())) .findFirst() @@ -232,13 +254,19 @@ public abstract class DataStorage { return entry; } - public Optional getStoreEntryIfPresent(@NonNull DataStore store) { + public synchronized Optional getStoreEntryIfPresent(@NonNull DataStore store) { var entry = storeEntries.stream().filter(n -> store.equals(n.getStore())).findFirst(); return entry; } - public Optional getDataSource(DataSourceReference ref) { + public synchronized Optional getStoreEntryIfPresent(@NonNull String name) { + return storeEntries.stream() + .filter(n -> n.getName().equalsIgnoreCase(name)) + .findFirst(); + } + + public synchronized Optional getDataSource(DataSourceReference ref) { Objects.requireNonNull(ref, "ref"); switch (ref.getType()) { @@ -275,7 +303,7 @@ public abstract class DataStorage { throw new AssertionError(); } - public DataSourceId getId(DataSourceEntry entry) { + public synchronized DataSourceId getId(DataSourceEntry entry) { Objects.requireNonNull(entry, "entry"); if (!sourceEntries.contains(entry)) { throw new IllegalArgumentException("Entry not in storage"); @@ -290,7 +318,7 @@ public abstract class DataStorage { return DataSourceId.create(col, en); } - public void add(DataSourceEntry e, DataSourceCollection c) { + public synchronized void add(DataSourceEntry e, DataSourceCollection c) { Objects.requireNonNull(e, "entry"); if (c != null && !sourceCollections.contains(c)) { @@ -339,8 +367,8 @@ public abstract class DataStorage { }); } - private void propagateUpdate() { - for (DataStoreEntry dataStoreEntry : getStores()) { + private synchronized void propagateUpdate() { + for (DataStoreEntry dataStoreEntry : getStoreEntries()) { dataStoreEntry.simpleRefresh(); } @@ -349,8 +377,8 @@ public abstract class DataStorage { } } - public void addStoreEntry(@NonNull DataStoreEntry e) { - if (getStoreIfPresent(e.getName()).isPresent()) { + public synchronized void addStoreEntry(@NonNull DataStoreEntry e) { + if (getStoreEntryIfPresent(e.getName()).isPresent()) { throw new IllegalArgumentException("Store with name " + e.getName() + " already exists"); } @@ -362,7 +390,7 @@ public abstract class DataStorage { this.listeners.forEach(l -> l.onStoreAdd(e)); } - public void addStoreIfNotPresent(@NonNull String name, DataStore store) { + public synchronized void addStoreEntryIfNotPresent(@NonNull String name, DataStore store) { if (getStoreEntryIfPresent(store).isPresent()) { return; } @@ -371,27 +399,29 @@ public abstract class DataStorage { addStoreEntry(e); } - public DataStoreEntry addStore(@NonNull String name, DataStore store) { + public synchronized DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) { var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store); addStoreEntry(e); return e; } - public void deleteStoreEntry(@NonNull DataStoreEntry store) { + public synchronized void deleteStoreEntry(@NonNull DataStoreEntry store) { if (!store.getConfiguration().isDeletable()) { throw new UnsupportedOperationException(); } this.storeEntries.remove(store); + propagateUpdate(); save(); + this.listeners.forEach(l -> l.onStoreRemove(store)); } - public void addListener(StorageListener l) { + public synchronized void addListener(StorageListener l) { this.listeners.add(l); } - public DataSourceCollection createOrGetCollection(String name) { + public synchronized DataSourceCollection createOrGetCollection(String name) { return getCollectionForName(name).orElseGet(() -> { var col = DataSourceCollection.createNew(name); addCollection(col); @@ -399,7 +429,7 @@ public abstract class DataStorage { }); } - public void addCollection(DataSourceCollection c) { + public synchronized void addCollection(DataSourceCollection c) { checkImmutable(); c.setDirectory( @@ -408,16 +438,16 @@ public abstract class DataStorage { this.listeners.forEach(l -> l.onCollectionAdd(c)); } - public void deleteCollection(DataSourceCollection c) { + public synchronized void deleteCollection(DataSourceCollection c) { checkImmutable(); this.sourceCollections.remove(c); this.listeners.forEach(l -> l.onCollectionRemove(c)); - c.getEntries().forEach(this::deleteEntry); + c.getEntries().forEach(this::deleteSourceEntry); } - public void deleteEntry(DataSourceEntry e) { + public synchronized void deleteSourceEntry(DataSourceEntry e) { checkImmutable(); this.sourceEntries.remove(e); @@ -430,56 +460,42 @@ public abstract class DataStorage { public abstract void load(); - public void refresh() { + public synchronized void refresh() { storeEntries.forEach(entry -> { - try { - entry.refresh(false); - } catch (Exception e) { - ErrorEvent.fromThrowable(e).omit().reportable(false).handle(); - } + entry.simpleRefresh(); }); save(); } public abstract void save(); - public Optional getStoreEntryByUuid(UUID id) { + public synchronized Optional getStoreEntry(UUID id) { return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); } - public Optional getSourceEntryByUuid(UUID id) { + public synchronized Optional getSourceEntry(UUID id) { return sourceEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); } - public Optional getDataSourceEntryById(DataSourceId id) { - return sourceEntries.stream().filter(e -> getId(e).equals(id)).findAny(); - } - - public Optional getEntryBySource(DataSource source) { + public synchronized Optional getSourceEntry(DataSource source) { return sourceEntries.stream() .filter(e -> e.getSource() != null && e.getSource().equals(source)) .findAny(); } - public Optional getEntryByStore(DataStore store) { - return storeEntries.stream() - .filter(e -> e.getStore() != null && e.getStore().equals(store)) - .findAny(); - } - - public Optional getCollectionByUuid(UUID id) { + public synchronized Optional getCollection(UUID id) { return sourceCollections.stream().filter(e -> e.getUuid().equals(id)).findAny(); } - public List getSourceEntries() { - return Collections.unmodifiableList(sourceEntries); + public synchronized List getSourceEntries() { + return new ArrayList<>(sourceEntries); } - public List getSourceCollections() { - return Collections.unmodifiableList(sourceCollections); + public synchronized List getSourceCollections() { + return new ArrayList<>(sourceCollections); } - public List getStores() { - return Collections.unmodifiableList(storeEntries); + public synchronized List getStoreEntries() { + return new ArrayList<>(storeEntries); } } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java b/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java index 14da7f95..0e3a97f4 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java @@ -52,7 +52,7 @@ public class DataStorageParser { return Optional.empty(); } - var entry = DataStorage.get().getStoreEntryByUuid(id); + var entry = DataStorage.get().getStoreEntry(id); if (entry.isEmpty()) { TrackEvent.withWarn("storage", "Encountered unknown store").tag("id", id); return Optional.empty(); @@ -70,7 +70,7 @@ public class DataStorageParser { }); node = replaceReferenceIdsForType(node, "sourceId", id -> { - var foundEntry = DataStorage.get().getSourceEntryByUuid(id); + var foundEntry = DataStorage.get().getSourceEntry(id); if (foundEntry.isPresent()) { var sourceNode = mapper.valueToTree(foundEntry.get().getSource()); // return Optional.of(resolvesReferenceIds(sourceNode)); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageWriter.java b/app/src/main/java/io/xpipe/app/storage/DataStorageWriter.java index f881496b..867bac95 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageWriter.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageWriter.java @@ -38,8 +38,12 @@ public class DataStorageWriter { try { var store = mapper.treeToValue(possibleReference, DataStore.class); + if (store == null) { + return Optional.empty(); + } + if (!isRoot) { - var found = DataStorage.get().getEntryByStore(store); + var found = DataStorage.get().getStoreEntryIfPresent(store); return found.map(dataSourceEntry -> dataSourceEntry.getUuid()); } } catch (JsonProcessingException e) { @@ -55,7 +59,7 @@ public class DataStorageWriter { try { var source = mapper.treeToValue(possibleReference, DataSource.class); if (!isRoot) { - var found = DataStorage.get().getEntryBySource(source); + var found = DataStorage.get().getSourceEntry(source); return found.map(dataSourceEntry -> dataSourceEntry.getUuid()); } } catch (JsonProcessingException e) { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 26d780ae..4303f301 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -93,7 +93,6 @@ public class DataStoreEntry extends StorageElement { Configuration configuration) { var entry = new DataStoreEntry( directory, uuid, name, lastUsed, lastModified, information, storeNode, false, state, configuration); - entry.refresh(false); return entry; } @@ -236,7 +235,7 @@ public class DataStoreEntry extends StorageElement { information = null; throw e; } finally { - listeners.forEach(l -> l.onUpdate()); + propagateUpdate(); } } } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index ab995458..91544361 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -45,7 +45,7 @@ public class StandardStorage extends DataStorage { return; } - var entry = getStoreEntryByUuid(uuid); + var entry = getStoreEntry(uuid); if (entry.isEmpty()) { TrackEvent.withTrace("storage", "Deleting leftover store directory") .tag("uuid", uuid) @@ -77,7 +77,7 @@ public class StandardStorage extends DataStorage { return; } - var entry = getSourceEntryByUuid(uuid); + var entry = getSourceEntry(uuid); if (entry.isEmpty()) { TrackEvent.withTrace("storage", "Deleting leftover entry directory") .tag("uuid", uuid) @@ -109,7 +109,7 @@ public class StandardStorage extends DataStorage { return; } - var col = getCollectionByUuid(uuid); + var col = getCollection(uuid); if (col.isEmpty()) { TrackEvent.withTrace("storage", "Deleting leftover collection directory") .tag("uuid", uuid) @@ -125,7 +125,7 @@ public class StandardStorage extends DataStorage { } } - public void load() { + public synchronized void load() { var newSession = isNewSession(); var entriesDir = getSourcesDir().resolve("entries"); var collectionsDir = getSourcesDir().resolve("collections"); @@ -236,7 +236,7 @@ public class StandardStorage extends DataStorage { deleteLeftovers(); } - public void save() { + public synchronized void save() { var entriesDir = getSourcesDir().resolve("entries"); var collectionsDir = getSourcesDir().resolve("collections"); diff --git a/app/src/main/java/io/xpipe/app/update/XPipeInstanceHelper.java b/app/src/main/java/io/xpipe/app/update/XPipeInstanceHelper.java index 1041a3e4..fbc2fccc 100644 --- a/app/src/main/java/io/xpipe/app/update/XPipeInstanceHelper.java +++ b/app/src/main/java/io/xpipe/app/update/XPipeInstanceHelper.java @@ -60,7 +60,7 @@ public class XPipeInstanceHelper { } public static XPipeInstance refresh() { - Map> map = DataStorage.get().getStores().stream() + Map> map = DataStorage.get().getStoreEntries().stream() .filter(entry -> entry.getStore() instanceof ShellStore) .collect(Collectors.toMap( entry -> entry.getStore().asNeeded(), diff --git a/app/src/main/java/io/xpipe/app/util/DialogHelper.java b/app/src/main/java/io/xpipe/app/util/DialogHelper.java index bd2090f0..c939796c 100644 --- a/app/src/main/java/io/xpipe/app/util/DialogHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DialogHelper.java @@ -1,5 +1,6 @@ package io.xpipe.app.util; +import io.xpipe.app.storage.DataStorage; import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.charsetter.StreamCharset; import io.xpipe.core.dialog.Dialog; @@ -32,7 +33,7 @@ public class DialogHelper { return new LocalStore(); } - var stored = XPipeDaemon.getInstance().getNamedStore(name); + var stored = DataStorage.get().getStoreEntryIfPresent(name).map(entry -> entry.getStore()); if (stored.isEmpty()) { throw new IllegalArgumentException(String.format("Store not found: %s", name)); } @@ -57,7 +58,7 @@ public class DialogHelper { return new LocalStore(); } - var stored = XPipeDaemon.getInstance().getNamedStore(name); + var stored = DataStorage.get().getStoreEntryIfPresent(name).map(entry -> entry.getStore()); if (stored.isEmpty()) { throw new IllegalArgumentException(String.format("Store not found: %s", name)); } @@ -98,7 +99,7 @@ public class DialogHelper { var name = XPipeDaemon.getInstance().getStoreName(store).orElse(null); return Dialog.query("Store", false, true, false, name, QueryConverter.STRING) .map((String newName) -> { - var found = XPipeDaemon.getInstance().getNamedStore(newName).orElseThrow(); + var found = DataStorage.get().getStoreEntryIfPresent(newName).map(entry -> entry.getStore()).orElseThrow(); if (!filter.isAssignableFrom(found.getClass())) { throw new IllegalArgumentException("Incompatible store type"); } diff --git a/app/src/main/java/io/xpipe/app/util/Validators.java b/app/src/main/java/io/xpipe/app/util/Validators.java index 504340c1..acc76fcc 100644 --- a/app/src/main/java/io/xpipe/app/util/Validators.java +++ b/app/src/main/java/io/xpipe/app/util/Validators.java @@ -1,7 +1,7 @@ package io.xpipe.app.util; import io.xpipe.app.core.AppI18n; -import io.xpipe.core.impl.LocalStore; +import io.xpipe.app.storage.DataStorage; import io.xpipe.core.store.DataStore; import io.xpipe.core.store.ShellStore; import io.xpipe.core.util.ValidationException; @@ -22,7 +22,7 @@ public class Validators { } public static void namedStoreExists(DataStore store, String name) throws ValidationException { - if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) { + if (!DataStorage.get().getUsableStores().contains(store)) { throw new ValidationException(AppI18n.get("app.missingStore", name)); } } diff --git a/app/src/main/java/io/xpipe/app/util/XPipeDaemon.java b/app/src/main/java/io/xpipe/app/util/XPipeDaemon.java index 3f273e17..5a74f923 100644 --- a/app/src/main/java/io/xpipe/app/util/XPipeDaemon.java +++ b/app/src/main/java/io/xpipe/app/util/XPipeDaemon.java @@ -1,19 +1,10 @@ package io.xpipe.app.util; -import io.xpipe.app.ext.DataSourceProvider; -import io.xpipe.app.ext.DataStoreProvider; -import io.xpipe.app.fxcomps.Comp; import io.xpipe.core.source.DataSource; -import io.xpipe.core.source.DataSourceType; import io.xpipe.core.store.DataStore; -import javafx.beans.property.Property; -import javafx.beans.value.ObservableValue; -import javafx.scene.image.Image; -import java.util.List; import java.util.Optional; import java.util.ServiceLoader; -import java.util.function.Predicate; public interface XPipeDaemon { @@ -25,35 +16,6 @@ public interface XPipeDaemon { return ServiceLoader.load(XPipeDaemon.class).findFirst(); } - List getNamedStores(); - - String getVersion(); - - Image image(String file); - - String svgImage(String file); - - & Validatable> T streamStoreChooser( - Property storeProperty, - Property> provider, - boolean showAnonymous, - boolean showSaved); - - & Validatable> T namedStoreChooser( - ObservableValue> filter, - Property selected, - DataStoreProvider.DataCategory category); - - & Validatable> T namedSourceChooser( - ObservableValue>> filter, - Property> selected, - DataSourceProvider.Category category); - - & Validatable> T sourceProviderChooser( - Property> provider, DataSourceProvider.Category category, DataSourceType filter); - - Optional getNamedStore(String name); - Optional> getSource(String id); Optional getStoreName(DataStore store); diff --git a/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java b/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java index fae54b11..6a29c3af 100644 --- a/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java +++ b/app/src/main/java/io/xpipe/app/util/XPipeDaemonProvider.java @@ -1,102 +1,15 @@ package io.xpipe.app.util; -import io.xpipe.app.comp.source.DsProviderChoiceComp; -import io.xpipe.app.comp.source.NamedSourceChoiceComp; -import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp; -import io.xpipe.app.comp.source.store.NamedStoreChoiceComp; -import io.xpipe.app.core.AppImages; -import io.xpipe.app.core.AppProperties; -import io.xpipe.app.ext.DataSourceProvider; -import io.xpipe.app.ext.DataStoreProvider; -import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.storage.DataStoreEntry; -import io.xpipe.app.update.AppDownloads; 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 javafx.beans.property.Property; -import javafx.beans.value.ObservableValue; -import javafx.scene.image.Image; -import java.util.List; import java.util.Optional; -import java.util.function.Predicate; public class XPipeDaemonProvider implements XPipeDaemon { - @Override - public List getNamedStores() { - return DataStorage.get().getStores().stream() - .filter(entry -> !entry.isDisabled()) - .map(DataStoreEntry::getStore) - .toList(); - } - - @Override - public String getVersion() { - var version = AppProperties.get().getVersion() != null - ? AppProperties.get().getVersion() - : AppDownloads.getLatestVersion(); - return version; - } - - @Override - public Image image(String file) { - return AppImages.image(file); - } - - @Override - public String svgImage(String file) { - return AppImages.svgImage(file); - } - - @Override - @SuppressWarnings("unchecked") - public & Validatable> T streamStoreChooser( - Property storeProperty, - Property> provider, - boolean showAnonymous, - boolean showSaved) { - return (T) new DsStreamStoreChoiceComp( - storeProperty, provider, showAnonymous, showSaved, DsStreamStoreChoiceComp.Mode.WRITE); - } - - @Override - @SuppressWarnings("unchecked") - public & Validatable> T namedStoreChooser( - ObservableValue> filter, - Property selected, - DataStoreProvider.DataCategory category) { - return (T) new NamedStoreChoiceComp(filter, selected, category); - } - - @Override - @SuppressWarnings("unchecked") - public & Validatable> T namedSourceChooser( - ObservableValue>> filter, - Property> selected, - DataSourceProvider.Category category) { - return (T) new NamedSourceChoiceComp(filter, selected, category); - } - - @Override - @SuppressWarnings("unchecked") - public & Validatable> T sourceProviderChooser( - Property> provider, DataSourceProvider.Category category, DataSourceType filter) { - return (T) new DsProviderChoiceComp(category, provider, filter); - } - - @Override - public Optional getNamedStore(String name) { - if (name == null) { - return Optional.empty(); - } - return DataStorage.get().getStoreIfPresent(name).map(DataStoreEntry::getStore); - } - @Override public Optional> getSource(String id) { var sourceId = DataSourceId.fromString(id); @@ -111,7 +24,7 @@ public class XPipeDaemonProvider implements XPipeDaemon { return Optional.empty(); } - return DataStorage.get().getStores().stream() + return DataStorage.get().getStoreEntries().stream() .filter(entry -> !entry.isDisabled() && entry.getStore().equals(store)) .findFirst() .map(entry -> entry.getName()); @@ -119,7 +32,7 @@ public class XPipeDaemonProvider implements XPipeDaemon { @Override public Optional getSourceId(DataSource source) { - var entry = DataStorage.get().getEntryBySource(source); + var entry = DataStorage.get().getSourceEntry(source); return entry.map( dataSourceEntry -> DataStorage.get().getId(dataSourceEntry).toString()); } diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties index 2b8224ad..f2dc5f0d 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/intro_en.properties @@ -15,6 +15,7 @@ dataSourceIntroCollection=Collection data sources contain multiple sub data sour storeIntroTitle=Adding Connections storeIntroDescription=Connect to remote systems, databases, and more. storeStreamDescription=Stream connections produce raw byte data\nthat can be used to construct data sources from. -storeMachineDescription=Shell connections allow you to access local and remote shells.\nThrough them, you can then interact with any remote machine. +storeMachineDescription=You can quickly search for available remote connections automatically.\nAlternatively, you can also of course add them manually. +detectConnections=Detect connections storeDatabaseDescription=Database connections allow you to connect to\na database server and interact with its contained data. storeDocumentation=In case you prefer a more structured approach to\nfamiliarizing yourself with X-Pipe, check out the documentation: diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index 01875989..b7cfeafd 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -42,11 +42,6 @@ -fx-padding: 0; } -.loading-comp .spinner { --jfx-radius: 2.15em; --fx-background-color:transparent; -} - .loading-comp { -fx-background-color: #FFFFFFAA; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/FileStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/FileStoreProvider.java index a5e88015..29114468 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/FileStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/FileStoreProvider.java @@ -1,11 +1,11 @@ package io.xpipe.ext.base; +import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp; import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.util.DataStoreFormatter; import io.xpipe.app.util.DialogHelper; import io.xpipe.app.util.SimpleValidator; -import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.dialog.Dialog; import io.xpipe.core.impl.FileStore; import io.xpipe.core.impl.LocalStore; @@ -24,7 +24,8 @@ public class FileStoreProvider implements DataStoreProvider { @Override public GuiDialog guiDialog(Property store) { var val = new SimpleValidator(); - var comp = XPipeDaemon.getInstance().streamStoreChooser(store, null, false, false); + var comp = new DsStreamStoreChoiceComp( + store, null, false, false, DsStreamStoreChoiceComp.Mode.WRITE); return new GuiDialog(comp, val); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java index c135a336..3dc4a861 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java @@ -4,7 +4,6 @@ import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.StorageElement; -import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.impl.LocalStore; import io.xpipe.core.process.OsType; import io.xpipe.core.store.DataStore; @@ -33,7 +32,7 @@ public class LocalStoreProvider implements DataStoreProvider { @Override public void storageInit() throws Exception { - var hasLocal = XPipeDaemon.getInstance().getNamedStores().stream() + var hasLocal = DataStorage.get().getUsableStores().stream() .anyMatch(dataStore -> dataStore instanceof LocalStore); if (hasLocal) { return; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/actions/DeleteStoreChildrenAction.java b/ext/base/src/main/java/io/xpipe/ext/base/actions/DeleteStoreChildrenAction.java new file mode 100644 index 00000000..73fcd4ac --- /dev/null +++ b/ext/base/src/main/java/io/xpipe/ext/base/actions/DeleteStoreChildrenAction.java @@ -0,0 +1,66 @@ +package io.xpipe.ext.base.actions; + +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.core.store.DataStore; +import javafx.beans.value.ObservableValue; +import lombok.Value; + +public class DeleteStoreChildrenAction implements ActionProvider { + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public boolean requiresPlatform() { + return false; + } + + @Override + public void execute() throws Exception { + DataStorage.get().getStoreChildren(store,true).forEach(entry -> { + DataStorage.get().deleteStoreEntry(entry); + }); + } + } + + @Override + public DataStoreCallSite getDataStoreCallSite() { + return new DataStoreCallSite() { + + @Override + public boolean isMajor() { + return false; + } + + @Override + public ActionProvider.Action createAction(DataStore store) { + return new Action(DataStorage.get().getStoreEntry(store)); + } + + @Override + public Class getApplicableClass() { + return DataStore.class; + } + + @Override + public boolean isApplicable(DataStore o) throws Exception { + return DataStorage.get().getStoreChildren(DataStorage.get().getStoreEntry(o),true).size() > 1; + } + + @Override + public ObservableValue getName(DataStore store) { + return AppI18n.observable("base.deleteChildren"); + } + + @Override + public String getIcon(DataStore store) { + return "mdal-delete_outline"; + } + }; + } +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java index cd32c48d..45e55ddb 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/actions/EditStoreAction.java @@ -43,7 +43,7 @@ public class EditStoreAction implements ActionProvider { @Override public ActionProvider.Action createAction(DataStore store) { - return new Action(DataStorage.get().getStore(store)); + return new Action(DataStorage.get().getStoreEntry(store)); } @Override @@ -53,7 +53,7 @@ public class EditStoreAction implements ActionProvider { @Override public boolean isApplicable(DataStore o) throws Exception { - return DataStorage.get().getStore(o).getConfiguration().isEditable(); + return DataStorage.get().getStoreEntry(o).getConfiguration().isEditable(); } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/apps/DataSourceOutputTarget.java b/ext/base/src/main/java/io/xpipe/ext/base/apps/DataSourceOutputTarget.java index fe2271e3..709f5b33 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/apps/DataSourceOutputTarget.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/apps/DataSourceOutputTarget.java @@ -1,6 +1,7 @@ package io.xpipe.ext.base.apps; import io.xpipe.app.comp.source.GuiDsTableMappingConfirmation; +import io.xpipe.app.comp.source.NamedSourceChoiceComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.DataSourceProvider; import io.xpipe.app.ext.DataSourceProviders; @@ -10,7 +11,6 @@ import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ChainedValidator; import io.xpipe.app.util.DynamicOptionsBuilder; -import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.source.*; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; @@ -34,8 +34,7 @@ public class DataSourceOutputTarget implements DataSourceTarget { var target = new SimpleObjectProperty>(); var sourceType = DataSourceProviders.byDataSourceClass(source.getClass()).getPrimaryType(); - var chooser = XPipeDaemon.getInstance() - .namedSourceChooser( + var chooser = new NamedSourceChoiceComp( new SimpleObjectProperty<>(s -> s != source && s.getFlow().hasOutput() && DataSourceProviders.byDataSourceClass(s.getClass()) diff --git a/ext/base/src/main/java/io/xpipe/ext/base/apps/FileOutputTarget.java b/ext/base/src/main/java/io/xpipe/ext/base/apps/FileOutputTarget.java index c6d074ab..a4d2bbac 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/apps/FileOutputTarget.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/apps/FileOutputTarget.java @@ -1,5 +1,7 @@ package io.xpipe.ext.base.apps; +import io.xpipe.app.comp.source.DsProviderChoiceComp; +import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp; import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.DataSourceProvider; @@ -12,7 +14,6 @@ import io.xpipe.app.fxcomps.util.SimpleChangeListener; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ChainedValidator; import io.xpipe.app.util.DynamicOptionsBuilder; -import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.WriteMode; @@ -67,8 +68,7 @@ public class FileOutputTarget implements DataSourceTarget { }); var layout = new BorderPane(); - var providerChoice = XPipeDaemon.getInstance() - .sourceProviderChooser(provider, DataSourceProvider.Category.STREAM, sourceProvider.getPrimaryType()); + var providerChoice = new DsProviderChoiceComp(DataSourceProvider.Category.STREAM, provider, sourceProvider.getPrimaryType()); providerChoice.apply(GrowAugment.create(true, false)); var providerChoiceRegion = providerChoice.createRegion(); var top = new VBox(providerChoiceRegion, new Separator()); @@ -76,7 +76,7 @@ public class FileOutputTarget implements DataSourceTarget { layout.setTop(top); layout.getStyleClass().add("data-input-creation-step"); - var chooser = XPipeDaemon.getInstance().streamStoreChooser(target, provider, true, true); + var chooser = new DsStreamStoreChoiceComp(target, provider, true, true, DsStreamStoreChoiceComp.Mode.WRITE); var mode = new SimpleObjectProperty(); var modeComp = new WriteModeChoiceComp(mode, availableModes); target.addListener((c, o, n) -> { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java b/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java index 9bf5a0fe..ba187343 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/apps/RawFileOutputTarget.java @@ -1,11 +1,11 @@ package io.xpipe.ext.base.apps; +import io.xpipe.app.comp.source.store.NamedStoreChoiceComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.DataSourceTarget; import io.xpipe.app.ext.DataStoreProvider; import io.xpipe.app.fxcomps.augment.GrowAugment; import io.xpipe.app.util.DynamicOptionsBuilder; -import io.xpipe.app.util.XPipeDaemon; import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSourceId; import io.xpipe.core.store.StreamDataStore; @@ -25,8 +25,7 @@ public class RawFileOutputTarget implements DataSourceTarget { public InstructionsDisplay createRetrievalInstructions(DataSource source, ObservableValue id) { var target = new SimpleObjectProperty(); - var storeChoice = XPipeDaemon.getInstance() - .namedStoreChooser( + var storeChoice = new NamedStoreChoiceComp( new SimpleObjectProperty<>(store -> store instanceof StreamDataStore && (store.getFlow().hasOutput())), target, diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index f675389f..e93d2492 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -22,6 +22,7 @@ open module io.xpipe.ext.base { requires org.apache.commons.lang3; provides ActionProvider with + DeleteStoreChildrenAction, AddStoreAction, EditStoreAction, StreamExportAction, diff --git a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties index 631688c0..b53947cf 100644 --- a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties +++ b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties @@ -12,6 +12,7 @@ options=Options copyShareLink=Copy share link selectStore=Select Store saveSource=Save for later +deleteChildren=Delete children selectSource=Select Source commandLineRead=Update commandLineWrite=Write diff --git a/version b/version index 167b000b..389faccc 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.4 \ No newline at end of file +0.5.5 \ No newline at end of file