From 8e6500d93fba3a547902db6443415ee339dd1c5e Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sun, 27 Jul 2025 18:50:13 +0200 Subject: [PATCH] Implement update button that updates and respawns the app --- .../cryptomator/ui/fxapp/UpdateChecker.java | 57 +++++++++++++++---- .../ui/mainwindow/MainWindowController.java | 2 +- .../UpdatesPreferencesController.java | 47 ++++++++++++++- .../resources/fxml/preferences_updates.fxml | 13 ++++- 4 files changed, 103 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java index 5e8f6eff7..b5c8579ba 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java @@ -5,12 +5,16 @@ import org.cryptomator.common.SemVerComparator; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.updates.AppUpdateChecker; import org.cryptomator.integrations.common.DistributionChannel; +import org.cryptomator.integrations.update.Progress; +import org.cryptomator.integrations.update.ProgressListener; import org.cryptomator.integrations.update.UpdateFailedException; +import org.cryptomator.ui.preferences.UpdatesPreferencesController; import org.purejava.portal.rest.UpdateCheckerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; @@ -44,12 +48,14 @@ public class UpdateChecker { private final BooleanBinding appUpdateAvailable; private final BooleanBinding checkFailed; private final AppUpdateChecker updateChecker; + private final FxApplicationTerminator appTerminator; @Inject UpdateChecker(Settings settings, // Environment env, // - ScheduledService updateCheckerService, - AppUpdateChecker updateChecker) { + ScheduledService updateCheckerService, // + AppUpdateChecker updateChecker, // + FxApplicationTerminator appTerminator) { this.env = env; this.settings = settings; this.updateCheckerService = updateCheckerService; @@ -58,23 +64,28 @@ public class UpdateChecker { this.appUpdateAvailable = Bindings.createBooleanBinding(this::isAppUpdateAvailable, latestAppUpdaterVersion); this.checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, state); this.updateChecker = updateChecker; + this.appTerminator = appTerminator; } public void automaticallyCheckForUpdatesIfEnabled() { if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) { - if (updateChecker.isUpdateServiceAvailable(env.getBuildNumber())) { // prefer AppUpdateChecker - switch (env.getBuildNumber().get()) { - case "flatpak-1" -> startCheckingWithFlatpakUpdater((UpdateCheckerTask) updateChecker.getUpdater(DistributionChannel.Value.LINUX_FLATPAK), AUTO_CHECK_DELAY); - default -> LOG.error("Unexpected value 'buildNumber': {}", env.getBuildNumber().get()); - } - } else { // fallback is the "redirect user to website" approach - startCheckingForUpdates(AUTO_CHECK_DELAY); - } + decideOnUpdateChecker(); } } public void checkForUpdatesNow() { - startCheckingForUpdates(Duration.ZERO); + decideOnUpdateChecker(); + } + + private void decideOnUpdateChecker() { + if (updateChecker.isUpdateServiceAvailable(env.getBuildNumber())) { // prefer AppUpdateChecker + switch (env.getBuildNumber().get()) { + case "flatpak-1" -> startCheckingWithFlatpakUpdater((UpdateCheckerTask) updateChecker.getUpdater(DistributionChannel.Value.LINUX_FLATPAK), Duration.ZERO); + default -> LOG.error("Unexpected value 'buildNumber': {}", env.getBuildNumber().get()); + } + } else { // fallback is the "redirect user to website" approach + startCheckingForUpdates(Duration.ZERO); + } } public void updateAppNow() throws UpdateFailedException { @@ -82,6 +93,30 @@ public class UpdateChecker { service.triggerUpdate(); } + public void terminateFlatpakOnUpdateCompleted(Runnable onComplete, UpdatesPreferencesController controller) { + var service = updateChecker.getServiceForChannel(DistributionChannel.Value.LINUX_FLATPAK); + service.addProgressListener(new ProgressListener() { + @Override + public void onProgress(Progress progress) { + LOG.debug("Update progess is at percentage: {} and has status: {}", progress.getProgress(), progress.getStatus()); + + if (progress.getStatus() == 0 || progress.getStatus() == 2) { + controller.flatpakProgressProperty().set(progress.getProgress() / 100.0); + } + + if (progress.getStatus() == 2 && progress.getProgress() == 100) { + LOG.debug("Update successfully finished, restarting App now"); + service.removeProgressListener(this); + if (onComplete != null) { + Platform.runLater(onComplete); + } + service.spawnApp(); + appTerminator.terminate(); + } + } + }); + } + private void startCheckingForUpdates(Duration initialDelay) { updateCheckerService.cancel(); updateCheckerService.reset(); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java index c6e084518..ba7fa43be 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java @@ -51,7 +51,7 @@ public class MainWindowController implements FxController { this.selectedVault = selectedVault; this.settings = settings; this.appWindows = appWindows; - this.updateAvailable = updateChecker.updateAvailableProperty(); + this.updateAvailable = updateChecker.updateAvailableProperty().or(updateChecker.appUpdateAvailableProperty()); this.licenseHolder = licenseHolder; updateChecker.automaticallyCheckForUpdatesIfEnabled(); diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 3c69db335..19ab9ea1c 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -2,9 +2,13 @@ package org.cryptomator.ui.preferences; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.updates.AppUpdateChecker; +import org.cryptomator.integrations.common.DistributionChannel; import org.cryptomator.integrations.update.UpdateFailedException; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.UpdateChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.animation.PauseTransition; @@ -14,12 +18,16 @@ import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; +import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ProgressBar; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -35,6 +43,7 @@ import java.util.ResourceBundle; @PreferencesScoped public class UpdatesPreferencesController implements FxController { + private static final Logger LOG = LoggerFactory.getLogger(UpdatesPreferencesController.class); private static final String DOWNLOADS_URI_TEMPLATE = "https://cryptomator.org/downloads/" // + "?utm_source=cryptomator-desktop" // + "&utm_medium=update-notification&" // @@ -44,7 +53,9 @@ public class UpdatesPreferencesController implements FxController { private final Environment environment; private final ResourceBundle resourceBundle; private final Settings settings; + private final Environment env; private final UpdateChecker updateChecker; + private final AppUpdateChecker appUpdateChecker; private final ObjectBinding checkForUpdatesButtonState; private final ReadOnlyStringProperty latestVersion; private final ObservableValue lastSuccessfulUpdateCheck; @@ -58,17 +69,23 @@ public class UpdatesPreferencesController implements FxController { private final DateTimeFormatter formatter; private final BooleanBinding upToDate; private final String downloadsUri; + private final BooleanProperty updatingFlatpak = new SimpleBooleanProperty(false); + private final DoubleProperty flatpakProgress = new SimpleDoubleProperty(ProgressBar.INDETERMINATE_PROGRESS); /* FXML */ public CheckBox checkForUpdatesCheckbox; + @FXML + public Button flatpakUpdateButton; @Inject - UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker) { + UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker, AppUpdateChecker appUpdateChecker, Environment env) { this.application = application; this.environment = environment; this.resourceBundle = resourceBundle; this.settings = settings; + this.env = env; this.updateChecker = updateChecker; + this.appUpdateChecker = appUpdateChecker; this.checkForUpdatesButtonState = Bindings.when(updateChecker.checkingForUpdatesProperty()).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY); this.latestVersion = updateChecker.latestVersionProperty(); this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty(); @@ -85,6 +102,10 @@ public class UpdatesPreferencesController implements FxController { public void initialize() { checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates); + switch (env.getBuildNumber().get()) { + case "flatpak-1" -> flatpakUpdateButton.setText(appUpdateChecker.getServiceForChannel(DistributionChannel.Value.LINUX_FLATPAK).getDisplayName()); + default -> LOG.error("Unexpected value 'buildNumber': {}", env.getBuildNumber().get()); + } upToDate.addListener((_, _, newVal) -> { if (newVal) { @@ -102,8 +123,17 @@ public class UpdatesPreferencesController implements FxController { } @FXML - public void updateNow() throws UpdateFailedException { - updateChecker.updateAppNow(); + public void updateFlatpakNow() { + updatingFlatpak.set(true); + updateChecker.terminateFlatpakOnUpdateCompleted( + () -> updatingFlatpak.set(false), this + ); + + try { + updateChecker.updateAppNow(); + } catch (UpdateFailedException e) { + updatingFlatpak.set(false); + } } @FXML @@ -202,4 +232,15 @@ public class UpdatesPreferencesController implements FxController { return checkFailed.getValue(); } + public BooleanProperty updatingFlatpakProperty() { + return updatingFlatpak; + } + + public boolean isUpdatingFlatpak() { + return updatingFlatpak.get(); + } + + public DoubleProperty flatpakProgressProperty() { + return flatpakProgress; + } } diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index c0141f074..96b40b82b 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -13,6 +13,7 @@ + - + +