Wire UpdateService

This commit is contained in:
Ralph Plawetzki
2025-07-20 08:23:50 +02:00
parent 510e134605
commit 89ce99deaf
5 changed files with 89 additions and 13 deletions

View File

@@ -50,6 +50,7 @@ open module org.cryptomator.desktop {
requires io.github.coffeelibs.tinyoauth2client;
requires org.slf4j;
requires org.apache.commons.lang3;
requires org.purejava.portal;
/* dagger bs */
requires jakarta.inject;

View File

@@ -35,7 +35,7 @@ public class AppUpdateChecker {
}
}
public String checkForUpdates(DistributionChannel.Value channel) {
public Object getUpdater(DistributionChannel.Value channel) {
if (updateServices.isEmpty()) {
LOG.error("No UpdateService found");
return null;
@@ -47,7 +47,7 @@ public class AppUpdateChecker {
LOG.error("Required service for channel LINUX_FLATPAK not available");
return null;
} else {
return flatpakService.isUpdateAvailable(DistributionChannel.Value.LINUX_FLATPAK);
return flatpakService.getLatestReleaseChecker(DistributionChannel.Value.LINUX_FLATPAK);
}
}
default -> throw new IllegalStateException("Unexpected value 'channel': " + channel);
@@ -68,4 +68,10 @@ public class AppUpdateChecker {
}).findFirst().orElse(null);
}
public UpdateService getServiceForChannel(DistributionChannel.Value requiredChannel) {
return updateServices.stream().filter(service -> {
DistributionChannel annotation = service.getClass().getAnnotation(DistributionChannel.class);
return annotation != null && annotation.value() == requiredChannel;
}).findFirst().orElse(null);
}
}

View File

@@ -5,7 +5,8 @@ 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.UpdateService;
import org.cryptomator.integrations.update.UpdateFailedException;
import org.purejava.portal.rest.UpdateCheckerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +23,7 @@ import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.util.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
@FxApplicationScoped
@@ -33,11 +35,13 @@ public class UpdateChecker {
private final Environment env;
private final Settings settings;
private final StringProperty latestVersion = new SimpleStringProperty();
private final StringProperty latestAppUpdaterVersion = new SimpleStringProperty();
private final ScheduledService<String> updateCheckerService;
private final ObjectProperty<UpdateCheckState> state = new SimpleObjectProperty<>(UpdateCheckState.NOT_CHECKED);
private final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
private final Comparator<String> versionComparator = new SemVerComparator();
private final BooleanBinding updateAvailable;
private final BooleanBinding appUpdateAvailable;
private final BooleanBinding checkFailed;
private final AppUpdateChecker updateChecker;
@@ -51,6 +55,7 @@ public class UpdateChecker {
this.updateCheckerService = updateCheckerService;
this.lastSuccessfulUpdateCheck = settings.lastSuccessfulUpdateCheck;
this.updateAvailable = Bindings.createBooleanBinding(this::isUpdateAvailable, latestVersion);
this.appUpdateAvailable = Bindings.createBooleanBinding(this::isAppUpdateAvailable, latestAppUpdaterVersion);
this.checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, state);
this.updateChecker = updateChecker;
}
@@ -58,14 +63,11 @@ public class UpdateChecker {
public void automaticallyCheckForUpdatesIfEnabled() {
if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) {
if (updateChecker.isUpdateServiceAvailable(env.getBuildNumber())) { // prefer AppUpdateChecker
String version = "";
switch (env.getBuildNumber().get()) {
case "flatpak-1" -> version = updateChecker.checkForUpdates(DistributionChannel.Value.LINUX_FLATPAK);
case "flatpak-1" -> startCheckingWithFlatpakUpdater((UpdateCheckerTask) updateChecker.getUpdater(DistributionChannel.Value.LINUX_FLATPAK), AUTO_CHECK_DELAY);
default -> LOG.error("Unexpected value 'buildNumber': {}", env.getBuildNumber().get());
}
LOG.info("Retrieved version from Update Service {}", version);
} else { // fallback is the "redirect user to website" approach
LOG.info("Common \"redirect user to website\" approach");
startCheckingForUpdates(AUTO_CHECK_DELAY);
}
}
@@ -75,6 +77,11 @@ public class UpdateChecker {
startCheckingForUpdates(Duration.ZERO);
}
public void updateAppNow() throws UpdateFailedException {
var service = updateChecker.getServiceForChannel(DistributionChannel.Value.LINUX_FLATPAK);
service.triggerUpdate();
}
private void startCheckingForUpdates(Duration initialDelay) {
updateCheckerService.cancel();
updateCheckerService.reset();
@@ -85,11 +92,31 @@ public class UpdateChecker {
updateCheckerService.start();
}
private void startCheckingWithFlatpakUpdater(UpdateCheckerTask service, Duration initialDelay) {
service.cancel();
service.reset();
service.setDelay(convertFxToJavaTime(initialDelay));
service.setOnRunning(this::checkStarted);
service.setOnSucceeded(this::checkSucceeded);
service.setOnFailed(this::checkFailed);
service.start();
}
private java.time.Duration convertFxToJavaTime(javafx.util.Duration fxDuration) {
double millis = fxDuration.toMillis();
return java.time.Duration.of((long) millis, ChronoUnit.MILLIS);
}
private void checkStarted(WorkerStateEvent event) {
LOG.debug("Checking for updates...");
state.set(UpdateCheckState.IS_CHECKING);
}
private void checkStarted() {
LOG.debug("Checking for updates...");
state.set(UpdateCheckState.IS_CHECKING);
}
private void checkSucceeded(WorkerStateEvent event) {
var latestVersionString = updateCheckerService.getValue();
LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), latestVersionString);
@@ -98,15 +125,26 @@ public class UpdateChecker {
state.set(UpdateCheckState.CHECK_SUCCESSFUL);
}
private void checkSucceeded(String version) {
LOG.info("Current version: {}, latest version: {}", getCurrentVersion(), version);
lastSuccessfulUpdateCheck.set(Instant.now());
latestAppUpdaterVersion.set(version);
state.set(UpdateCheckState.CHECK_SUCCESSFUL);
}
private void checkFailed(WorkerStateEvent event) {
state.set(UpdateCheckState.CHECK_FAILED);
}
private void checkFailed(Throwable throwable) {
state.set(UpdateCheckState.CHECK_FAILED);
}
public enum UpdateCheckState {
NOT_CHECKED,
IS_CHECKING,
CHECK_SUCCESSFUL,
CHECK_FAILED;
CHECK_FAILED
}
/* Observable Properties */
@@ -118,23 +156,38 @@ public class UpdateChecker {
return latestVersion;
}
public ReadOnlyStringProperty latestAppUpdaterVersionProperty() {
return latestAppUpdaterVersion;
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public BooleanBinding appUpdateAvailableProperty() {
return appUpdateAvailable;
}
public BooleanBinding checkFailedProperty() {
return checkFailed;
}
public boolean isUpdateAvailable() {
public boolean isUpdateAvailable(StringProperty versionProperty) {
String currentVersion = getCurrentVersion();
String latestVersionString = latestVersion.get();
String latestVersionString = versionProperty.get();
if (currentVersion == null || latestVersionString == null) {
return false;
} else {
return versionComparator.compare(currentVersion, latestVersionString) < 0;
}
return versionComparator.compare(currentVersion, latestVersionString) < 0;
}
public boolean isUpdateAvailable() {
return isUpdateAvailable(latestVersion);
}
public boolean isAppUpdateAvailable() {
return isUpdateAvailable(latestAppUpdaterVersion);
}
public ObjectProperty<Instant> lastSuccessfulUpdateCheckProperty() {

View File

@@ -2,6 +2,7 @@ package org.cryptomator.ui.preferences;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.update.UpdateFailedException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
@@ -51,6 +52,7 @@ public class UpdatesPreferencesController implements FxController {
private final ObservableValue<String> timeDifferenceMessage;
private final String currentVersion;
private final BooleanBinding updateAvailable;
private final BooleanBinding appUpdateAvailable;
private final BooleanBinding checkFailed;
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
private final DateTimeFormatter formatter;
@@ -73,6 +75,7 @@ public class UpdatesPreferencesController implements FxController {
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
this.currentVersion = environment.getAppVersion();
this.updateAvailable = updateChecker.updateAvailableProperty();
this.appUpdateAvailable = updateChecker.appUpdateAvailableProperty();
this.formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
this.upToDate = updateChecker.updateCheckStateProperty().isEqualTo(UpdateChecker.UpdateCheckState.CHECK_SUCCESSFUL).and(latestVersion.isEqualTo(currentVersion));
this.checkFailed = updateChecker.checkFailedProperty();
@@ -98,6 +101,11 @@ public class UpdatesPreferencesController implements FxController {
updateChecker.checkForUpdatesNow();
}
@FXML
public void updateNow() throws UpdateFailedException {
updateChecker.updateAppNow();
}
@FXML
public void visitDownloadsPage() {
application.getHostServices().showDocument(downloadsUri);
@@ -174,10 +182,18 @@ public class UpdatesPreferencesController implements FxController {
return updateAvailable;
}
public BooleanBinding appUdateAvailableProperty() {
return appUpdateAvailable;
}
public boolean isUpdateAvailable() {
return updateAvailable.get();
}
public boolean isAppUpdateAvailable() {
return appUpdateAvailable.get();
}
public BooleanBinding checkFailedProperty() {
return checkFailed;
}

View File

@@ -9,7 +9,6 @@
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.text.TextFlow?>
@@ -53,5 +52,6 @@
</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}"/>
</VBox>
</VBox>