Implement update button that updates and respawns the app

This commit is contained in:
Ralph Plawetzki
2025-07-27 18:50:13 +02:00
parent 89ce99deaf
commit 8e6500d93f
4 changed files with 103 additions and 16 deletions

View File

@@ -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<String> updateCheckerService,
AppUpdateChecker updateChecker) {
ScheduledService<String> 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();

View File

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

View File

@@ -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<ContentDisplay> checkForUpdatesButtonState;
private final ReadOnlyStringProperty latestVersion;
private final ObservableValue<Instant> 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;
}
}

View File

@@ -13,6 +13,7 @@
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.text.TextFlow?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.control.ProgressBar?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.preferences.UpdatesPreferencesController"
@@ -52,6 +53,16 @@
</graphic>
</Label>
<Hyperlink text="${linkLabel.value}" onAction="#visitDownloadsPage" textAlignment="CENTER" wrapText="true" styleClass="hyperlink-underline" visible="${controller.updateAvailable}" managed="${controller.updateAvailable}"/>
<Button text="Hello Flatpak!" onAction="#updateNow" visible="${controller.appUpdateAvailable}"/>
<Button fx:id="flatpakUpdateButton"
onAction="#updateFlatpakNow"
visible="${controller.appUpdateAvailable}">
</Button>
<ProgressBar fx:id="flatpakProgressBar"
maxWidth="200"
maxHeight="12"
visible="${controller.updatingFlatpak}"
managed="${controller.updatingFlatpak}"
progress="${controller.flatpakProgress}"/>
</VBox>
</VBox>