Rework states, fixed children, and storage logic [stage]

This commit is contained in:
crschnick 2023-08-05 10:13:17 +00:00
parent 5c6b98fd14
commit b0881a2909
21 changed files with 229 additions and 118 deletions

View file

@ -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"

View file

@ -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<CompStructure<StackPane>> {
@ -25,34 +27,35 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
@Override
public CompStructure<StackPane> 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<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> 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<CompStructure<StackPane>> {
}
if (showLoading.getValue()) {
Platform.runLater(() -> loadingBg.setVisible(true));
Platform.runLater(() -> loadingOverlay.setVisible(true));
}
});
}
@ -69,12 +72,23 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
};
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);

View file

@ -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)

View file

@ -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;
}

View file

@ -26,7 +26,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
private final DataStoreEntry entry;
private final Property<Instant> lastAccess;
private final BooleanProperty disabled = new SimpleBooleanProperty();
private final BooleanProperty loading = new SimpleBooleanProperty();
private final BooleanProperty validating = new SimpleBooleanProperty();
private final Property<DataStoreEntry.State> 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));

View file

@ -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);
}

View file

@ -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<BeaconClient.FailableRunnable<Exception>> post = new AtomicReference<>();
AtomicReference<FailableRunnable<Exception>> post = new AtomicReference<>();
var res = prov.get()
.handleRequest(
new BeaconHandler() {
@Override
public void postResponse(BeaconClient.FailableRunnable<Exception> r) {
public void postResponse(FailableRunnable<Exception> r) {
post.set(r);
}

View file

@ -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<Throwable> UNREPORTABLE = new CopyOnWriteArraySet<>();
private static Map<Throwable, ErrorEventBuilder> EVENT_BASES = new ConcurrentHashMap<>();
public static <T extends Throwable> 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 extends Throwable> T unreportableIf(T t, boolean b) {
if (b) {
UNREPORTABLE.add(t);
EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).unreportable());
}
return t;
}
public static <T extends Throwable> T unreportable(T t) {
UNREPORTABLE.add(t);
EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).unreportable());
return t;
}
}

View file

@ -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<String, FixedChildStore> 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<DataStoreEntry> 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<DataStoreEntry> 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 -> {
<T extends Throwable> void propagateUpdate(FailableRunnable<T> 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));
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -276,24 +276,6 @@ public class BeaconClient implements AutoCloseable {
return out;
}
@FunctionalInterface
public interface FailableBiConsumer<T, U, E extends Throwable> {
void accept(T var1, U var2) throws E;
}
@FunctionalInterface
public interface FailableConsumer<T, E extends Throwable> {
void accept(T var1) throws E;
}
@FunctionalInterface
public interface FailableRunnable<E extends Throwable> {
void run() throws E;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public abstract static class ClientInformation {

View file

@ -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<OutputStream, IOException> ex) {
public void withOutputStream(FailableConsumer<OutputStream, IOException> ex) {
try {
ex.accept(getOutputStream());
} catch (IOException e) {
@ -43,7 +45,7 @@ public abstract class BeaconConnection implements AutoCloseable {
}
}
public void withInputStream(BeaconClient.FailableConsumer<InputStream, IOException> ex) {
public void withInputStream(FailableConsumer<InputStream, IOException> ex) {
try {
ex.accept(getInputStream());
} catch (IOException e) {
@ -78,7 +80,7 @@ public abstract class BeaconConnection implements AutoCloseable {
}
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputExchange(
REQ req, BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
REQ req, FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed();
performInputOutputExchange(req, null, responseConsumer);
@ -86,8 +88,8 @@ public abstract class BeaconConnection implements AutoCloseable {
public <REQ extends RequestMessage, RES extends ResponseMessage> void performInputOutputExchange(
REQ req,
BeaconClient.FailableConsumer<OutputStream, IOException> reqWriter,
BeaconClient.FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
FailableConsumer<OutputStream, IOException> reqWriter,
FailableBiConsumer<RES, InputStream, Exception> responseConsumer) {
checkClosed();
try {
@ -149,7 +151,7 @@ public abstract class BeaconConnection implements AutoCloseable {
}
public <REQ extends RequestMessage, RES extends ResponseMessage> RES performOutputExchange(
REQ req, BeaconClient.FailableConsumer<OutputStream, Exception> reqWriter) {
REQ req, FailableConsumer<OutputStream, Exception> reqWriter) {
checkClosed();
try {

View file

@ -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<Exception> r);
void postResponse(FailableRunnable<Exception> r);
/**
* Prepares to attach a body to a response.

View file

@ -0,0 +1,6 @@
package io.xpipe.core.store;
public interface FixedChildStore extends DataStore {
int getFixedId();
}

View file

@ -4,5 +4,5 @@ import java.util.Map;
public interface FixedHierarchyStore extends DataStore {
Map<String, DataStore> listChildren() throws Exception;
Map<String, FixedChildStore> listChildren() throws Exception;
}

View file

@ -0,0 +1,7 @@
package io.xpipe.core.util;
@FunctionalInterface
public interface FailableBiConsumer<T, U, E extends Throwable> {
void accept(T var1, U var2) throws E;
}

View file

@ -0,0 +1,7 @@
package io.xpipe.core.util;
@FunctionalInterface
public interface FailableConsumer<T, E extends Throwable> {
void accept(T var1) throws E;
}

View file

@ -0,0 +1,7 @@
package io.xpipe.core.util;
@FunctionalInterface
public interface FailableRunnable<E extends Throwable> {
void run() throws E;
}

View file

@ -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