diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java index 1177de3c2..fcd1ed9c1 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkList.java @@ -131,7 +131,7 @@ final class BrowserBookmarkList extends SimpleComp { ThreadHelper.runFailableAsync(() -> { BusyProperty.execute(busy, () -> { - getItem().refreshIfNeeded(); + getItem().executeRefreshAction(); }); if (getItem().getEntry().getStore() instanceof ShellStore fileSystem) { model.openFileSystemAsync(null, fileSystem, null, busy); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java index 2e6f3c5cf..6d03384e1 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java @@ -79,7 +79,6 @@ public abstract class StoreEntryComp extends SimpleComp { button.setOnAction(event -> { event.consume(); ThreadHelper.runFailableAsync(() -> { - entry.refreshIfNeeded(); entry.executeDefaultAction(); }); }); 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 04de0fe35..e69c20993 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 @@ -8,6 +8,8 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.store.DataStore; import io.xpipe.core.store.FixedHierarchyStore; import javafx.beans.property.*; import lombok.Getter; @@ -161,28 +163,32 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { }); } - public void refreshIfNeeded() throws Exception { + public void executeRefreshAction() throws Exception { var found = getDefaultActionProvider().getValue(); - if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID) || found == null) { - getEntry().refresh(true); - PlatformThread.runLaterIfNeeded(() -> { - expanded.set(true); - }); - } + getEntry().refresh(true); + var hasChildren = DataStorage.get().refreshChildren(entry); + PlatformThread.runLaterIfNeeded(() -> { + expanded.set(hasChildren); + }); + } + + public void mutate(DataStore newValue) { + ThreadHelper.runAsync(() -> { + DataStorage.get().setAndRefreshAsync(getEntry(), newValue); + }); } public void executeDefaultAction() throws Exception { var found = getDefaultActionProvider().getValue(); if (found != null) { + if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID) || entry.getState().equals(DataStoreEntry.State.COMPLETE_NOT_VALIDATED)) { + executeRefreshAction(); + } + entry.updateLastUsed(); found.createAction(entry.getStore().asNeeded()).execute(); } else if (getEntry().getStore() instanceof FixedHierarchyStore) { - var hasChildren = DataStorage.get().refreshChildren(entry); - if (!hasChildren) { - PlatformThread.runLaterIfNeeded(() -> { - expanded.set(false); - }); - } + executeRefreshAction(); } } diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java index 40daab844..a61a15fc5 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreIntroComp.java @@ -3,6 +3,7 @@ package io.xpipe.app.comp.storage.store; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.SimpleComp; +import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.Hyperlinks; import io.xpipe.app.util.ScanAlert; import io.xpipe.core.impl.LocalStore; @@ -35,7 +36,7 @@ public class StoreIntroComp extends SimpleComp { }); var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify")); - scanButton.setOnAction(event -> ScanAlert.showAsync(new LocalStore(), false)); + scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().getStoreEntry(new LocalStore()), false)); var scanPane = new StackPane(scanButton); scanPane.setAlignment(Pos.CENTER); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java index 6ea59ef06..0213bce58 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreSection.java @@ -46,7 +46,7 @@ public class StoreSection implements StorageFilter.Filterable { var parent = section.getWrapper() .getEntry() .getProvider() - .getLogicalParent(section.getWrapper().getEntry().getStore()); + .getDisplayParent(section.getWrapper().getEntry().getStore()); return parent == null || (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty()); }); diff --git a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java index c7b93bf17..a36038fb6 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/store/GuiDsStoreCreator.java @@ -123,7 +123,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { e -> { try { DataStorage.get().addStoreEntry(e); - // ScanAlert.showAsync(e.getStore(), true); + ScanAlert.showAsync(e, true); } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); } diff --git a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java index 216e0e698..729a5b374 100644 --- a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java @@ -1,8 +1,8 @@ package io.xpipe.app.ext; +import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.core.process.ShellControl; import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.ShellStore; import io.xpipe.core.util.ModuleLayerLoader; import lombok.Value; import org.apache.commons.lang3.function.FailableRunnable; @@ -53,7 +53,7 @@ public abstract class ScanProvider { return null; } - public ScanOperation create(ShellStore store, ShellControl sc, boolean automatic) throws Exception { + public ScanOperation create(DataStoreEntry entry, ShellControl sc, boolean automatic) throws Exception { return null; } } 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 435b644aa..789c272ff 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -68,7 +68,8 @@ public abstract class DataStorage { } public synchronized boolean refreshChildren(DataStoreEntry e) { - return refreshChildren(e, List.of()); + var children = getLogicalStoreChildren(e, false); + return refreshChildren(e, children); } public synchronized boolean refreshChildren(DataStoreEntry e, List oldChildren) { @@ -93,11 +94,11 @@ public abstract class DataStorage { } public synchronized void deleteChildren(DataStoreEntry e, boolean deep) { - getLogicalStoreChildren(e, deep).forEach(entry -> { - if (!entry.getConfiguration().isDeletable()) { - return; - } + // Reverse to delete deepest children first + var ordered = getLogicalStoreChildren(e, deep); + Collections.reverse(ordered); + ordered.forEach(entry -> { deleteStoreEntry(entry); }); } @@ -191,7 +192,7 @@ public abstract class DataStorage { .findFirst(); } - public void updateAndRefreshAsync(DataStoreEntry entry, DataStore s) { + public void setAndRefreshAsync(DataStoreEntry entry, DataStore s) { ThreadHelper.runAsync(() -> { var old = entry.getStore(); var children = getLogicalStoreChildren(entry, false); 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 fb722db48..d8d63fd13 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -258,7 +258,7 @@ public class DataStoreEntry extends StorageElement { dirty = true; } else if (complete) { var stateToUse = state == State.LOAD_FAILED || state == State.INCOMPLETE - ? State.COMPLETE_BUT_INVALID + ? State.COMPLETE_NOT_VALIDATED : state; state = stateToUse; } else { @@ -331,6 +331,8 @@ public class DataStoreEntry extends StorageElement { LOAD_FAILED(false), @JsonProperty("incomplete") INCOMPLETE(false), + @JsonProperty("completeNotValidated") + COMPLETE_NOT_VALIDATED(true), @JsonProperty("completeButInvalid") COMPLETE_BUT_INVALID(true), @JsonProperty("validating") diff --git a/app/src/main/java/io/xpipe/app/util/ScanAlert.java b/app/src/main/java/io/xpipe/app/util/ScanAlert.java index 94c355350..dc9d3d3e8 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanAlert.java +++ b/app/src/main/java/io/xpipe/app/util/ScanAlert.java @@ -9,7 +9,7 @@ import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.impl.LabelComp; import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.issue.ErrorEvent; -import io.xpipe.core.store.DataStore; +import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.core.store.ShellStore; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; @@ -29,34 +29,34 @@ import java.util.function.Supplier; public class ScanAlert { - public static void showAsync(DataStore store, boolean automatic) { + public static void showAsync(DataStoreEntry entry, boolean automatic) { ThreadHelper.runAsync(() -> { - if (store instanceof ShellStore) { - showForShellStore(store.asNeeded(), automatic); + if (entry.getStore() instanceof ShellStore) { + showForShellStore(entry, automatic); } else { - showForOtherStore(store, automatic); + showForOtherStore(entry, automatic); } }); } - private static void showForOtherStore(DataStore store, boolean automatic) { + private static void showForOtherStore(DataStoreEntry entry, boolean automatic) { showIfNeeded(() -> { var providers = ScanProvider.getAll(); var applicable = providers.stream() - .map(scanProvider -> scanProvider.create(store, automatic)) + .map(scanProvider -> scanProvider.create(entry.getStore(), automatic)) .filter(scanOperation -> scanOperation != null) .toList(); return applicable; }); } - private static void showForShellStore(ShellStore store, boolean automatic) { + private static void showForShellStore(DataStoreEntry entry, boolean automatic) { showIfNeeded(() -> { - try (var sc = store.control().start()) { + try (var sc = ((ShellStore) entry.getStore()).control().start()) { var providers = ScanProvider.getAll(); var applicable = new ArrayList(); for (ScanProvider scanProvider : providers) { - ScanProvider.ScanOperation operation = scanProvider.create(store, sc, automatic); + ScanProvider.ScanOperation operation = scanProvider.create(entry, sc, automatic); if (operation != null) { applicable.add(operation); } @@ -94,16 +94,21 @@ public class ScanAlert { // Custom behavior for ok button var btOk = (Button) alert.getDialogPane().lookupButton(ButtonType.OK); btOk.addEventFilter(ActionEvent.ACTION, event -> { - BusyProperty.execute(busy, () -> { - for (var a : selected) { - try { - a.getScanner().run(); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).handle(); + ThreadHelper.runAsync(() -> { + BusyProperty.execute(busy, () -> { + for (var a : selected) { + try { + a.getScanner().run(); + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).handle(); + } } - } - alert.setResult(ButtonType.OK); - alert.close(); + + Platform.runLater(() -> { + alert.setResult(ButtonType.OK); + alert.close(); + }); + }); }); });