diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java index a69384e41..0b593ef94 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java @@ -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> { private final Environment env; private final Settings settings; private final ObjectProperty lastSuccessfulUpdateCheck; - private final StringExpression latestVersion = StringExpression.stringExpression(lastValueProperty().map(UpdateInfo::version)); - private final BooleanBinding updateAvailable = lastValueProperty().isNotNull(); + private final ObjectProperty> update = new SimpleObjectProperty<>(); + private final StringExpression latestVersion = StringExpression.stringExpression(update.map(UpdateInfo::version)); + private final BooleanBinding updateAvailable = update.isNotNull(); private final ObjectBinding 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> { 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> { } } + 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> { 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> { /* Observable Properties */ + public UpdateInfo getUpdate() { + return update.get(); + } + + public ObjectProperty> updateProperty() { + return update; + } + public String getLatestVersion() { return latestVersion.get(); } @@ -160,14 +179,14 @@ public class UpdateChecker extends ScheduledService> { @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..."); diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 129abff3a..072b46f8c 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -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()).>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() {