From b0881a2909ff306d5d8411db882abe100db823aa Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 5 Aug 2023 10:13:17 +0000 Subject: [PATCH] Rework states, fixed children, and storage logic [stage] --- app/build.gradle | 2 +- .../app/comp/base/LoadingOverlayComp.java | 44 +++-- .../xpipe/app/comp/base/StoreToggleComp.java | 3 +- .../comp/storage/store/StoreEntryComp.java | 2 +- .../comp/storage/store/StoreEntryWrapper.java | 4 +- .../app/comp/storage/store/StoreSection.java | 3 +- .../io/xpipe/app/core/AppSocketServer.java | 5 +- .../java/io/xpipe/app/issue/ErrorEvent.java | 30 +++- .../io/xpipe/app/storage/DataStorage.java | 152 +++++++++++++----- .../io/xpipe/app/storage/DataStoreEntry.java | 28 ++-- .../io/xpipe/app/update/AppDownloads.java | 3 +- .../io/xpipe/app/update/UpdateHandler.java | 2 +- .../java/io/xpipe/beacon/BeaconClient.java | 18 --- .../io/xpipe/beacon/BeaconConnection.java | 14 +- .../java/io/xpipe/beacon/BeaconHandler.java | 4 +- .../io/xpipe/core/store/FixedChildStore.java | 6 + .../xpipe/core/store/FixedHierarchyStore.java | 2 +- .../xpipe/core/util/FailableBiConsumer.java | 7 + .../io/xpipe/core/util/FailableConsumer.java | 7 + .../io/xpipe/core/util/FailableRunnable.java | 7 + dist/changelogs/1.5.0.md | 4 +- 21 files changed, 229 insertions(+), 118 deletions(-) create mode 100644 core/src/main/java/io/xpipe/core/store/FixedChildStore.java create mode 100644 core/src/main/java/io/xpipe/core/util/FailableBiConsumer.java create mode 100644 core/src/main/java/io/xpipe/core/util/FailableConsumer.java create mode 100644 core/src/main/java/io/xpipe/core/util/FailableRunnable.java diff --git a/app/build.gradle b/app/build.gradle index 57e2ec8f2..356113c5f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -144,7 +144,7 @@ application { run { systemProperty 'io.xpipe.app.useVirtualThreads', 'false' systemProperty 'io.xpipe.app.mode', 'gui' - systemProperty 'io.xpipe.app.dataDir', "$projectDir/local7/" + // systemProperty 'io.xpipe.app.dataDir', "$projectDir/local7/" systemProperty 'io.xpipe.app.writeLogs', "true" systemProperty 'io.xpipe.app.writeSysOut', "true" systemProperty 'io.xpipe.app.developerMode', "true" 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 d8080897a..d2a5a3707 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 @@ -10,6 +10,8 @@ import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.StackPane; public class LoadingOverlayComp extends Comp> { @@ -25,34 +27,35 @@ public class LoadingOverlayComp extends Comp> { @Override public CompStructure createBase() { var compStruc = comp.createStructure(); + var r = compStruc.get(); var loading = new RingProgressIndicator(0, false); loading.setProgress(-1); - var loadingBg = new StackPane(loading); - loadingBg.getStyleClass().add("loading-comp"); - loadingBg.getStyleClass().add("modal-pane"); + var loadingOverlay = new StackPane(loading); + loadingOverlay.getStyleClass().add("loading-comp"); + loadingOverlay.getStyleClass().add("modal-pane"); + loadingOverlay.visibleProperty().addListener((observable, oldValue, newValue) -> { + r.setOpacity(newValue ? r.getOpacity() * 0.25 : Math.min(1.0, r.getOpacity() * 4)); + }); - loadingBg.setVisible(showLoading.getValue()); + loadingOverlay.setVisible(showLoading.getValue()); var listener = new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean busy) { if (!busy) { // Reduce flickering for consecutive loads - Thread t = new Thread(() -> { + ThreadHelper.runAsync(() -> { try { Thread.sleep(50); } catch (InterruptedException ignored) { } if (!showLoading.getValue()) { - Platform.runLater(() -> loadingBg.setVisible(false)); + Platform.runLater(() -> loadingOverlay.setVisible(false)); } }); - t.setDaemon(true); - t.setName("loading delay"); - t.start(); } else { ThreadHelper.runAsync(() -> { try { @@ -61,7 +64,7 @@ public class LoadingOverlayComp extends Comp> { } if (showLoading.getValue()) { - Platform.runLater(() -> loadingBg.setVisible(true)); + Platform.runLater(() -> loadingOverlay.setVisible(true)); } }); } @@ -69,12 +72,23 @@ public class LoadingOverlayComp extends Comp> { }; PlatformThread.sync(showLoading).addListener(listener); - var r = compStruc.get(); - var stack = new StackPane(r, loadingBg); + var stack = new StackPane(r, loadingOverlay); - loading.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> { - return Math.min(r.getHeight() - 20, 50); - }, r.heightProperty())); + r.backgroundProperty().addListener((observable, oldValue, newValue) -> { + loadingOverlay.setBackground(new Background(new BackgroundFill( + loadingOverlay.getBackground() != null ? loadingOverlay.getBackground().getFills().get( + 0 + ).getFill() : null, + newValue.getFills().get(0).getRadii(), + newValue.getFills().get(0).getInsets()))); + }); + + loading.prefWidthProperty() + .bind(Bindings.createDoubleBinding( + () -> { + return Math.min(r.getHeight() - 20, 50); + }, + r.heightProperty())); loading.prefHeightProperty().bind(loading.prefWidthProperty()); return new SimpleCompStructure<>(stack); diff --git a/app/src/main/java/io/xpipe/app/comp/base/StoreToggleComp.java b/app/src/main/java/io/xpipe/app/comp/base/StoreToggleComp.java index 27a452a7e..3405b43c9 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/StoreToggleComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/StoreToggleComp.java @@ -32,10 +32,11 @@ public class StoreToggleComp extends SimpleComp { var visible = BindingsHelper.persist(Bindings.createBooleanBinding( () -> { return (section.getWrapper().getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID - || section.getWrapper().getState().getValue() == DataStoreEntry.State.VALIDATING) + || section.getWrapper().getValidating().get()) && section.getShowDetails().get(); }, section.getWrapper().getState(), + section.getWrapper().getValidating(), section.getShowDetails())); var t = new NamedToggleComp(value, AppI18n.observable(nameKey)) .visible(visible) 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 1a0dbe2f9..b6c2c810e 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 @@ -83,7 +83,7 @@ public abstract class StoreEntryComp extends SimpleComp { }); new ContextMenuAugment<>(() -> this.createContextMenu()).augment(new SimpleCompStructure<>(button)); - var loading = new LoadingOverlayComp(Comp.of(() -> button), wrapper.getLoading()); + var loading = new LoadingOverlayComp(Comp.of(() -> button), wrapper.getValidating()); var region = loading.createRegion(); return region; } 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 140902aae..450c653a4 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 @@ -26,7 +26,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { private final DataStoreEntry entry; private final Property lastAccess; private final BooleanProperty disabled = new SimpleBooleanProperty(); - private final BooleanProperty loading = new SimpleBooleanProperty(); + private final BooleanProperty validating = new SimpleBooleanProperty(); private final Property state = new SimpleObjectProperty<>(); private final StringProperty information = new SimpleStringProperty(); private final StringProperty summary = new SimpleStringProperty(); @@ -96,7 +96,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { expanded.setValue(entry.isExpanded()); information.setValue(entry.getInformation()); - loading.setValue(entry.getState() == DataStoreEntry.State.VALIDATING); + validating.setValue(entry.isValidating()); if (entry.getState().isUsable()) { try { summary.setValue(entry.getProvider().toSummaryString(entry.getStore(), 50)); 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 b6ebb6a41..fc1bc6c83 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 @@ -4,6 +4,7 @@ import io.xpipe.app.comp.storage.StorageFilter; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntry; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableBooleanValue; @@ -68,7 +69,7 @@ public class StoreSection implements StorageFilter.Filterable { } private static StoreSection create(StoreEntryWrapper e, int depth) { - if (!e.getEntry().getState().isUsable()) { + if (e.getEntry().getState() == DataStoreEntry.State.LOAD_FAILED) { return new StoreSection(e, FXCollections.observableArrayList(), depth); } diff --git a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java index edfe834e1..712428803 100644 --- a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java +++ b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java @@ -14,6 +14,7 @@ import io.xpipe.beacon.exchange.MessageExchanges; import io.xpipe.beacon.exchange.data.ClientErrorMessage; import io.xpipe.beacon.exchange.data.ServerErrorMessage; import io.xpipe.core.util.Deobfuscator; +import io.xpipe.core.util.FailableRunnable; import io.xpipe.core.util.JacksonMapper; import java.io.IOException; @@ -132,12 +133,12 @@ public class AppSocketServer { if (prov.isEmpty()) { throw new IllegalArgumentException("Unknown request id: " + req.getClass()); } - AtomicReference> post = new AtomicReference<>(); + AtomicReference> post = new AtomicReference<>(); var res = prov.get() .handleRequest( new BeaconHandler() { @Override - public void postResponse(BeaconClient.FailableRunnable r) { + public void postResponse(FailableRunnable r) { post.set(r); } diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java b/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java index 5a24b4f6c..103449ed8 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java @@ -6,7 +6,7 @@ import lombok.Singular; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ConcurrentHashMap; @Builder @Getter @@ -37,13 +37,19 @@ public class ErrorEvent { } public static ErrorEventBuilder fromThrowable(Throwable t) { - var unreportable = UNREPORTABLE.remove(t); - return builder().throwable(t).reportable(!unreportable).description(ExceptionConverter.convertMessage(t)); + if (EVENT_BASES.containsKey(t)) { + return EVENT_BASES.remove(t).description(ExceptionConverter.convertMessage(t)); + } + + return builder().throwable(t).description(ExceptionConverter.convertMessage(t)); } public static ErrorEventBuilder fromThrowable(String msg, Throwable t) { - var unreportable = UNREPORTABLE.remove(t); - return builder().throwable(t).reportable(!unreportable).description(msg); + if (EVENT_BASES.containsKey(t)) { + return EVENT_BASES.remove(t).description(msg); + } + + return builder().throwable(t).description(msg); } public static ErrorEventBuilder fromMessage(String msg) { @@ -74,12 +80,20 @@ public class ErrorEvent { return omitted(true); } + public ErrorEventBuilder unreportable() { + return reportable(false); + } + + public ErrorEventBuilder discard() { + return omit().unreportable(); + } + public void handle() { build().handle(); } } - private static Set UNREPORTABLE = new CopyOnWriteArraySet<>(); + private static Map EVENT_BASES = new ConcurrentHashMap<>(); public static T unreportableIfEndsWith(T t, String... s) { return unreportableIf(t, t.getMessage() != null && Arrays.stream(s).anyMatch(string->t.getMessage().toLowerCase(Locale.ROOT).endsWith(string))); @@ -91,13 +105,13 @@ public class ErrorEvent { public static T unreportableIf(T t, boolean b) { if (b) { - UNREPORTABLE.add(t); + EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).unreportable()); } return t; } public static T unreportable(T t) { - UNREPORTABLE.add(t); + EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).unreportable()); return t; } } 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 b685c10c4..37ac7689a 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -7,7 +7,10 @@ import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.impl.LocalStore; import io.xpipe.core.source.DataStoreId; import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.FixedChildStore; import io.xpipe.core.store.FixedHierarchyStore; +import io.xpipe.core.util.FailableRunnable; +import javafx.util.Pair; import lombok.Getter; import lombok.NonNull; @@ -69,23 +72,81 @@ public abstract class DataStorage { return INSTANCE; } - public synchronized boolean refreshChildren(DataStoreEntry e) { + public boolean refreshChildren(DataStoreEntry e) { + return refreshChildren(e, null); + } + + public synchronized boolean refreshChildren(DataStoreEntry e, DataStore newValue) { if (!(e.getStore() instanceof FixedHierarchyStore)) { return false; } + var oldChildren = getStoreChildren(e, false, false); + Map newChildren; try { - deleteChildren(e, true); - var newChildren = ((FixedHierarchyStore) e.getStore()).listChildren(); - addStoreEntries(newChildren.entrySet().stream() - .map(stringDataStoreEntry -> DataStoreEntry.createNew( - UUID.randomUUID(), stringDataStoreEntry.getKey(), stringDataStoreEntry.getValue())) - .toArray(DataStoreEntry[]::new)); - return newChildren.size() > 0; + newChildren = ((FixedHierarchyStore) (newValue != null ? newValue : e.getStore())).listChildren(); } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); return false; } + + var toRemove = oldChildren.stream() + .filter(entry -> newChildren.entrySet().stream() + .noneMatch( + nc -> nc.getValue().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId())) + .toList(); + var toAdd = newChildren.entrySet().stream() + .filter(entry -> oldChildren.stream() + .noneMatch(oc -> ((FixedChildStore) oc.getStore()).getFixedId() + == entry.getValue().getFixedId())) + .toList(); + var toUpdate = oldChildren.stream() + .map(entry -> { + FixedChildStore found = newChildren.values().stream() + .filter(nc -> nc.getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()) + .findFirst() + .orElse(null); + return new Pair<>(entry, found); + }) + .filter(en -> en.getValue() != null) + .toList(); + + if (newValue != null) { + e.setStoreInternal(newValue, false); + } + + deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new)); + addStoreEntries(toAdd.stream() + .map(stringDataStoreEntry -> DataStoreEntry.createNew( + UUID.randomUUID(), stringDataStoreEntry.getKey(), stringDataStoreEntry.getValue())) + .toArray(DataStoreEntry[]::new)); + toUpdate.forEach(entry -> { + propagateUpdate( + () -> { + entry.getKey().setStoreInternal(entry.getValue(), false); + }, + entry.getKey()); + }); + save(); + return !newChildren.isEmpty(); + } + + public synchronized void deleteWithChildren(DataStoreEntry... entries) { + var toDelete = Arrays.stream(entries) + .flatMap(entry -> { + // Reverse to delete deepest children first + var ordered = getStoreChildren(entry, false, true); + Collections.reverse(ordered); + return ordered.stream(); + }) + .toList(); + + synchronized (this) { + toDelete.forEach(entry -> entry.finalizeEntry()); + this.storeEntries.removeAll(toDelete); + this.listeners.forEach(l -> l.onStoreRemove(toDelete.toArray(DataStoreEntry[]::new))); + } + save(); } public synchronized void deleteChildren(DataStoreEntry e, boolean deep) { @@ -117,23 +178,22 @@ public abstract class DataStorage { } public synchronized List getStoreChildren(DataStoreEntry entry, boolean display, boolean deep) { - if (!entry.getState().isUsable()) { + if (entry.getState() == DataStoreEntry.State.LOAD_FAILED) { return List.of(); } var children = new ArrayList<>(getStoreEntries().stream() .filter(other -> { - if (!other.getState().isUsable()) { + if (other.getState() == DataStoreEntry.State.LOAD_FAILED) { return false; } - var provider = other.getProvider(); - var parent = display - ? provider.getDisplayParent(other.getStore()) - : provider.getLogicalParent(other.getStore()); - return parent != null - && entry.getStore().getClass().equals(parent.getClass()) - && entry.getStore().equals(parent); + var parent = getParent(other, display); + return parent.isPresent() + && entry.getStore() + .getClass() + .equals(parent.get().getStore().getClass()) + && entry.getStore().equals(parent.get().getStore()); }) .toList()); @@ -208,7 +268,9 @@ public abstract class DataStorage { public synchronized DataStoreEntry getStoreEntry(@NonNull DataStore store) { var entry = storeEntries.stream() - .filter(n -> n.getStore() != null && Objects.equals(store.getClass(), n.getStore().getClass()) && store.equals(n.getStore())) + .filter(n -> n.getStore() != null + && Objects.equals(store.getClass(), n.getStore().getClass()) + && store.equals(n.getStore())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Store not found")); return entry; @@ -220,7 +282,11 @@ public abstract class DataStorage { for (int i = 1; i < id.getNames().size(); i++) { var children = getStoreChildren(current.get(), false, false); int finalI = i; - current = children.stream().filter(dataStoreEntry -> dataStoreEntry.getName().equalsIgnoreCase(id.getNames().get(finalI))).findFirst(); + current = children.stream() + .filter(dataStoreEntry -> dataStoreEntry + .getName() + .equalsIgnoreCase(id.getNames().get(finalI))) + .findFirst(); if (current.isEmpty()) { break; } @@ -234,8 +300,13 @@ public abstract class DataStorage { } public synchronized Optional getStoreEntryIfPresent(@NonNull DataStore store) { - var entry = - storeEntries.stream().filter(n -> store.equals(n.getStore())).findFirst(); + var entry = storeEntries.stream() + .filter(n -> { + return n.getStore() != null + && store.getClass().equals(n.getStore().getClass()) + && store.equals(n.getStore()); + }) + .findFirst(); return entry; } @@ -251,7 +322,7 @@ public abstract class DataStorage { try { entry.setStoreInternal(s, false); entry.refresh(true); - return DataStorage.get().refreshChildren(entry); + return DataStorage.get().refreshChildren(entry, s); } catch (Exception e) { entry.setStoreInternal(old, false); entry.simpleRefresh(); @@ -260,16 +331,19 @@ public abstract class DataStorage { } public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) { - newEntry.finalizeEntry(); - entry.applyChanges(newEntry); - entry.initializeEntry(); + propagateUpdate( + () -> { + newEntry.finalizeEntry(); + entry.applyChanges(newEntry); + entry.initializeEntry(); + }, + entry); } public void refreshAsync(DataStoreEntry element, boolean deep) { ThreadHelper.runAsync(() -> { try { - element.refresh(deep); - propagateUpdate(element); + propagateUpdate(() -> element.refresh(deep), element); } catch (Exception e) { ErrorEvent.fromThrowable(e).reportable(false).handle(); } @@ -277,21 +351,20 @@ public abstract class DataStorage { }); } - void propagateUpdate(DataStoreEntry origin) { - getStoreChildren(origin, false, false).forEach(entry -> { + void propagateUpdate(FailableRunnable runnable, DataStoreEntry origin) throws T { + var children = getStoreChildren(origin, false, true); + runnable.run(); + children.forEach(entry -> { entry.simpleRefresh(); - propagateUpdate(entry); }); } public void addStoreEntry(@NonNull DataStoreEntry e) { e.getProvider().preAdd(e.getStore()); - synchronized (this) { e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); this.storeEntries.add(e); } - propagateUpdate(e); save(); this.listeners.forEach(l -> l.onStoreAdd(e)); @@ -302,10 +375,8 @@ public abstract class DataStorage { synchronized (this) { for (DataStoreEntry e : es) { e.getProvider().preAdd(e.getStore()); - e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); this.storeEntries.add(e); - propagateUpdate(e); } this.listeners.forEach(l -> l.onStoreAdd(es)); for (DataStoreEntry e : es) { @@ -344,11 +415,14 @@ public abstract class DataStorage { } public void deleteStoreEntry(@NonNull DataStoreEntry store) { - store.finalizeEntry(); - synchronized (this) { - this.storeEntries.remove(store); - } - propagateUpdate(store); + propagateUpdate( + () -> { + store.finalizeEntry(); + synchronized (this) { + this.storeEntries.remove(store); + } + }, + store); save(); this.listeners.forEach(l -> l.onStoreRemove(store)); } 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 a4fef39e0..0d53a6c48 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -47,6 +47,9 @@ public class DataStoreEntry extends StorageElement { @NonFinal boolean expanded; + @NonFinal + boolean validating; + @NonFinal DataStoreProvider provider; @@ -102,13 +105,6 @@ public class DataStoreEntry extends StorageElement { State state, Configuration configuration, boolean expanded) { - - // The validation must be stuck if that happens - var stateToUse = state; - if (state == State.VALIDATING) { - stateToUse = State.COMPLETE_BUT_INVALID; - } - var entry = new DataStoreEntry( directory, uuid, @@ -118,7 +114,7 @@ public class DataStoreEntry extends StorageElement { information, storeNode, false, - stateToUse, + state, configuration, expanded); return entry; @@ -259,9 +255,10 @@ public class DataStoreEntry extends StorageElement { } if (complete && deep) { - state = State.VALIDATING; + validating = true; notifyListeners(); store.validate(); + validating = false; state = State.COMPLETE_AND_VALID; information = getProvider().queryInformationString(getStore(), 50); dirty = true; @@ -280,6 +277,7 @@ public class DataStoreEntry extends StorageElement { state = stateToUse; } } catch (Exception e) { + validating = false; state = State.COMPLETE_BUT_INVALID; information = getProvider().queryInvalidInformationString(getStore(), 50); throw e; @@ -293,12 +291,12 @@ public class DataStoreEntry extends StorageElement { public void initializeEntry() { if (store instanceof ExpandedLifecycleStore lifecycleStore) { try { - state = State.VALIDATING; + validating = true; notifyListeners(); lifecycleStore.initializeValidate(); - state = State.COMPLETE_AND_VALID; + validating = false; } catch (Exception e) { - state = State.COMPLETE_BUT_INVALID; + validating = false; ErrorEvent.fromThrowable(e).handle(); } finally { notifyListeners(); @@ -310,12 +308,10 @@ public class DataStoreEntry extends StorageElement { public void finalizeEntry() { if (store instanceof ExpandedLifecycleStore lifecycleStore) { try { - state = State.VALIDATING; + validating = true; notifyListeners(); lifecycleStore.finalizeValidate(); - state = State.COMPLETE_AND_VALID; } catch (Exception e) { - state = State.COMPLETE_BUT_INVALID; ErrorEvent.fromThrowable(e).handle(); } finally { notifyListeners(); @@ -379,8 +375,6 @@ public class DataStoreEntry extends StorageElement { COMPLETE_NOT_VALIDATED(true), @JsonProperty("completeButInvalid") COMPLETE_BUT_INVALID(true), - @JsonProperty("validating") - VALIDATING(true), @JsonProperty("completeAndValid") COMPLETE_AND_VALID(true); diff --git a/app/src/main/java/io/xpipe/app/update/AppDownloads.java b/app/src/main/java/io/xpipe/app/update/AppDownloads.java index f87eeb936..ab27d9fac 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDownloads.java +++ b/app/src/main/java/io/xpipe/app/update/AppDownloads.java @@ -33,8 +33,7 @@ public class AppDownloads { .withRateLimitHandler(RateLimitHandler.FAIL) .withAuthorizationProvider(AuthorizationProvider.ANONYMOUS) .build(); - repository = - github.getRepository(AppProperties.get().isStaging() ? "xpipe-io/xpipe_staging" : "xpipe-io/xpipe"); + repository = github.getRepository(AppProperties.get().isStaging() ? "xpipe-io/xpipe_staging" : "xpipe-io/xpipe"); return repository; } diff --git a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java index 0b6f7e7ec..a7f799e09 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java @@ -137,7 +137,7 @@ public abstract class UpdateHandler { try { return refreshUpdateCheck(); } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).omit().handle(); + ErrorEvent.fromThrowable(ex).discard().handle(); return null; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index 8cdb1e98c..706be511e 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -276,24 +276,6 @@ public class BeaconClient implements AutoCloseable { return out; } - @FunctionalInterface - public interface FailableBiConsumer { - - void accept(T var1, U var2) throws E; - } - - @FunctionalInterface - public interface FailableConsumer { - - void accept(T var1) throws E; - } - - @FunctionalInterface - public interface FailableRunnable { - - void run() throws E; - } - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public abstract static class ClientInformation { diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java index 504451dab..9018d6089 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConnection.java @@ -4,6 +4,8 @@ import io.xpipe.beacon.exchange.WriteStreamExchange; import io.xpipe.beacon.exchange.cli.StoreAddExchange; import io.xpipe.beacon.util.QuietDialogHandler; import io.xpipe.core.impl.InternalStreamStore; +import io.xpipe.core.util.FailableBiConsumer; +import io.xpipe.core.util.FailableConsumer; import java.io.IOException; import java.io.InputStream; @@ -35,7 +37,7 @@ public abstract class BeaconConnection implements AutoCloseable { } } - public void withOutputStream(BeaconClient.FailableConsumer ex) { + public void withOutputStream(FailableConsumer ex) { try { ex.accept(getOutputStream()); } catch (IOException e) { @@ -43,7 +45,7 @@ public abstract class BeaconConnection implements AutoCloseable { } } - public void withInputStream(BeaconClient.FailableConsumer ex) { + public void withInputStream(FailableConsumer ex) { try { ex.accept(getInputStream()); } catch (IOException e) { @@ -78,7 +80,7 @@ public abstract class BeaconConnection implements AutoCloseable { } public void performInputExchange( - REQ req, BeaconClient.FailableBiConsumer responseConsumer) { + REQ req, FailableBiConsumer responseConsumer) { checkClosed(); performInputOutputExchange(req, null, responseConsumer); @@ -86,8 +88,8 @@ public abstract class BeaconConnection implements AutoCloseable { public void performInputOutputExchange( REQ req, - BeaconClient.FailableConsumer reqWriter, - BeaconClient.FailableBiConsumer responseConsumer) { + FailableConsumer reqWriter, + FailableBiConsumer responseConsumer) { checkClosed(); try { @@ -149,7 +151,7 @@ public abstract class BeaconConnection implements AutoCloseable { } public RES performOutputExchange( - REQ req, BeaconClient.FailableConsumer reqWriter) { + REQ req, FailableConsumer reqWriter) { checkClosed(); try { diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java b/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java index ec8918b13..8b4a960b8 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconHandler.java @@ -1,5 +1,7 @@ package io.xpipe.beacon; +import io.xpipe.core.util.FailableRunnable; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,7 +16,7 @@ public interface BeaconHandler { * * @param r the runnable to execute */ - void postResponse(BeaconClient.FailableRunnable r); + void postResponse(FailableRunnable r); /** * Prepares to attach a body to a response. diff --git a/core/src/main/java/io/xpipe/core/store/FixedChildStore.java b/core/src/main/java/io/xpipe/core/store/FixedChildStore.java new file mode 100644 index 000000000..e20599812 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/FixedChildStore.java @@ -0,0 +1,6 @@ +package io.xpipe.core.store; + +public interface FixedChildStore extends DataStore { + + int getFixedId(); +} diff --git a/core/src/main/java/io/xpipe/core/store/FixedHierarchyStore.java b/core/src/main/java/io/xpipe/core/store/FixedHierarchyStore.java index 486034781..4fa4c380c 100644 --- a/core/src/main/java/io/xpipe/core/store/FixedHierarchyStore.java +++ b/core/src/main/java/io/xpipe/core/store/FixedHierarchyStore.java @@ -4,5 +4,5 @@ import java.util.Map; public interface FixedHierarchyStore extends DataStore { - Map listChildren() throws Exception; + Map listChildren() throws Exception; } diff --git a/core/src/main/java/io/xpipe/core/util/FailableBiConsumer.java b/core/src/main/java/io/xpipe/core/util/FailableBiConsumer.java new file mode 100644 index 000000000..508d8a496 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/FailableBiConsumer.java @@ -0,0 +1,7 @@ +package io.xpipe.core.util; + +@FunctionalInterface +public interface FailableBiConsumer { + + void accept(T var1, U var2) throws E; +} diff --git a/core/src/main/java/io/xpipe/core/util/FailableConsumer.java b/core/src/main/java/io/xpipe/core/util/FailableConsumer.java new file mode 100644 index 000000000..c3b3ea956 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/FailableConsumer.java @@ -0,0 +1,7 @@ +package io.xpipe.core.util; + +@FunctionalInterface +public interface FailableConsumer { + + void accept(T var1) throws E; +} diff --git a/core/src/main/java/io/xpipe/core/util/FailableRunnable.java b/core/src/main/java/io/xpipe/core/util/FailableRunnable.java new file mode 100644 index 000000000..12b2202c4 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/FailableRunnable.java @@ -0,0 +1,7 @@ +package io.xpipe.core.util; + +@FunctionalInterface +public interface FailableRunnable { + + void run() throws E; +} diff --git a/dist/changelogs/1.5.0.md b/dist/changelogs/1.5.0.md index c87dfb819..a90859874 100644 --- a/dist/changelogs/1.5.0.md +++ b/dist/changelogs/1.5.0.md @@ -1,6 +1,6 @@ ## Changes in 1.5.0 -This is the largest update yet and comes with loads of improvements and changes. There might be some rough edges, but these will be quickly ironed out. So please report any issues you can find! +This is the largest update yet and comes with loads of improvements and changes, some of which might require you to update some connection configurations. There might be some rough edges, but these will be quickly ironed out. So please report any issues you can find! ### Passwords & Password managers @@ -50,7 +50,6 @@ XPipe can now automatically detect Cygwin and MSYS2 environments on your machine ### Misc -- Separate staging and production storage directories - For every system, XPipe will now also display the appropriate OS/distro logo (if recognized) - Rework SSH key-based authentication to properly interact with agents, now also including pageant - Add ability to test out terminals and editors directly in the settings menu @@ -61,6 +60,7 @@ XPipe can now automatically detect Cygwin and MSYS2 environments on your machine - Add alternative ways of resolving path in case realpath was not available - Rework threading in navigation bar in browser to improve responsiveness - Recheck if prepared update is still the latest one prior to installing it +- Keep connection configuration when refreshing parent - Properly use shell script file extension for external editor when creating shell environments - Built-in documentation popups now honour the dark mode setting - Properly detect applications such as editors and terminals when they are present in the path on Windows