improved error handling

This commit is contained in:
Sebastian Stenzel
2025-11-06 10:47:15 +01:00
parent 26b69beb87
commit 9e4006cc89
2 changed files with 34 additions and 14 deletions

View File

@@ -15,6 +15,7 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
@@ -40,13 +41,14 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
private final Environment env;
private final Settings settings;
private final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
private final StringExpression latestVersion = StringExpression.stringExpression(lastValueProperty().map(UpdateInfo::version));
private final BooleanBinding updateAvailable = lastValueProperty().isNotNull();
private final ObjectProperty<UpdateInfo<?>> update = new SimpleObjectProperty<>();
private final StringExpression latestVersion = StringExpression.stringExpression(update.map(UpdateInfo::version));
private final BooleanBinding updateAvailable = update.isNotNull();
private final ObjectBinding<UpdateCheckState> updateState = Bindings.createObjectBinding(this::getUpdateCheckState, stateProperty());
private final BooleanBinding checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, updateState);
private final HttpClient httpClient;
private final UpdateMechanism<?> primaryUpdateMechanism;
private final UpdateMechanism<?> fallbackUpdateMechanism;
private UpdateMechanism<?> updateMechanism;
@Inject
UpdateChecker(Settings settings, //
@@ -63,9 +65,9 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
var currentVersion = env.getAppVersionWithBuildNumber();
var lastAttemptedBy = settings.lastUpdateAttemptedByVersion.get();
if (currentVersion != null && currentVersion.equals(lastAttemptedBy)) {
this.primaryUpdateMechanism = fallbackUpdateMechanism; // immediately use fallback mechanism
this.updateMechanism = fallbackUpdateMechanism; // immediately use fallback mechanism
} else {
this.primaryUpdateMechanism = UpdateMechanism.get().orElse(fallbackUpdateMechanism);
this.updateMechanism = UpdateMechanism.get().orElse(fallbackUpdateMechanism);
}
setExecutor(Executors.newVirtualThreadPerTaskExecutor());
@@ -78,6 +80,14 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
}
}
public void recheckWithFallbackMechanism() {
if (updateMechanism == fallbackUpdateMechanism) {
return; // already using fallback mechanism
}
updateMechanism = fallbackUpdateMechanism;
checkForUpdatesNow();
}
public void checkForUpdatesNow() {
startCheckingForUpdates(Duration.ZERO);
}
@@ -96,6 +106,7 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
lastSuccessfulUpdateCheck.set(Instant.now());
if (updateInfo != null) {
LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), updateInfo.version());
update.set(updateInfo);
}
}
@@ -106,6 +117,14 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
/* Observable Properties */
public UpdateInfo<?> getUpdate() {
return update.get();
}
public ObjectProperty<UpdateInfo<?>> updateProperty() {
return update;
}
public String getLatestVersion() {
return latestVersion.get();
}
@@ -160,14 +179,14 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
@Override
protected UpdateInfo<?> call() {
try {
var result = primaryUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
var result = updateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
if (result != null) {
return result;
}
} catch (UpdateFailedException e) {
LOG.error("Primary update check failed.", e);
LOG.error("Update check using {} failed.", updateMechanism.getClass(), e);
}
if (primaryUpdateMechanism == fallbackUpdateMechanism) {
if (updateMechanism == fallbackUpdateMechanism) {
return null;
}
LOG.debug("Trying fallback update check...");

View File

@@ -75,7 +75,7 @@ public class UpdatesPreferencesController implements FxController {
this.resourceBundle = resourceBundle;
this.settings = settings;
this.updateChecker = updateChecker;
this.updateService = new UpdateService(updateChecker.lastValueProperty());
this.updateService = new UpdateService(updateChecker.updateProperty());
this.vaultService = vaultService;
this.worker = Bindings.when(updateChecker.updateAvailableProperty()).<Worker<?>>then(this.updateService).otherwise(this.updateChecker);
this.running = Bindings.createBooleanBinding(this::isRunning, updateService.stateProperty(), updateChecker.stateProperty());
@@ -84,7 +84,7 @@ public class UpdatesPreferencesController implements FxController {
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
this.prohibitUpdateWhileUnlocked = Bindings.createBooleanBinding(this::isProhibitUpdateWhileUnlocked, unlockedVaults, updateChecker.lastValueProperty());
this.prohibitUpdateWhileUnlocked = Bindings.createBooleanBinding(this::isProhibitUpdateWhileUnlocked, unlockedVaults, updateChecker.updateProperty());
this.updateButtonDisabled = Bindings.when(worker.isEqualTo(updateChecker)).then(running).otherwise(prohibitUpdateWhileUnlocked.or(running));
}
@@ -114,7 +114,7 @@ public class UpdatesPreferencesController implements FxController {
} else if (!unlockedVaults.isEmpty()) {
LOG.warn("Cannot start update due to unlocked vaults.");
} else if (worker.get().equals(updateService)) {
LOG.info("User started update to version {}", updateChecker.getLastValue().version());
LOG.info("User started update to version {}", updateChecker.getUpdate().version());
updateService.start();
}
}
@@ -138,7 +138,8 @@ public class UpdatesPreferencesController implements FxController {
private void updateFailed(WorkerStateEvent workerStateEvent) {
assert workerStateEvent.getSource() == updateService;
LOG.error("Update failed.", updateService.getException());
// TODO: show error to user? use fallback update service?
// try fallback mechanism:
updateChecker.recheckWithFallbackMechanism();
updateService.reset();
}
@@ -178,7 +179,7 @@ public class UpdatesPreferencesController implements FxController {
return resourceBundle.getString("preferences.updates.checkNowBtn");
} else {
return switch (updateService.getState()) {
case READY -> updateChecker.getLastValue().updateMechanism().getName();
case READY -> updateChecker.getUpdate().updateMechanism().getName();
case SCHEDULED, RUNNING -> updateService.getMessage(); // "Preparing Update..."; // TODO: resourceBundle.getString("preferences.updates.preparingUpdate")...
case SUCCEEDED -> "Restart to Update"; // TODO: resourceBundle.getString("preferences.updates.readyToRestart")...
case FAILED, CANCELLED -> "failed";
@@ -234,7 +235,7 @@ public class UpdatesPreferencesController implements FxController {
public boolean isProhibitUpdateWhileUnlocked() {
// If the result of the last update check was from the fallback mechanism, we don't need to show the warning
return !unlockedVaults.isEmpty() && !FallbackUpdateInfo.class.isInstance(updateChecker.getLastValue());
return !unlockedVaults.isEmpty() && !FallbackUpdateInfo.class.isInstance(updateChecker.getUpdate());
}
public BooleanBinding prohibitUpdateWhileUnlockedProperty() {