From 02186ca17aae1aa6fa41f25ce0705bf20ab58929 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 6 Jul 2025 20:02:41 +0200 Subject: [PATCH] hooked up in UI --- .../UpdatesPreferencesController.java | 62 +++++++++++++++++++ .../cryptomator/updater/UpdateMechanism.java | 8 +-- .../resources/fxml/preferences_updates.fxml | 8 +++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index f5a72290f..54c1ac4d3 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -4,10 +4,15 @@ import org.cryptomator.common.Environment; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.UpdateChecker; +import org.cryptomator.updater.UpdateMechanism; +import org.cryptomator.updater.UpdateProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.animation.PauseTransition; import javafx.application.Application; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; @@ -16,9 +21,11 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableValue; +import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ContentDisplay; +import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -29,11 +36,14 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Locale; import java.util.ResourceBundle; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; @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&" // @@ -56,6 +66,9 @@ public class UpdatesPreferencesController implements FxController { private final DateTimeFormatter formatter; private final BooleanBinding upToDate; private final String downloadsUri; + private final UpdateMechanism updateMechanism; + public final Task updatePreparationTask; + private final StringBinding updateButtonTitle; /* FXML */ public CheckBox checkForUpdatesCheckbox; @@ -78,6 +91,18 @@ public class UpdatesPreferencesController implements FxController { this.checkFailed = updateChecker.checkFailedProperty(); this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck); this.downloadsUri = DOWNLOADS_URI_TEMPLATE.formatted(URLEncoder.encode(currentVersion, StandardCharsets.US_ASCII)); + this.updateMechanism = UpdateMechanism.get(); + this.updatePreparationTask = new Task<>() { // TODO custom class? + @Override + protected UpdateProcess call() throws IOException, InterruptedException { + var updateProcess = updateMechanism.prepareUpdate(); + do { + updateProgress(updateProcess.preparationProgress(), 1.0); + } while (!updateProcess.await(100, TimeUnit.MILLISECONDS)); + return updateProcess; + } + }; + this.updateButtonTitle = Bindings.createStringBinding(this::getUpdateButtonTitle, updatePreparationTask.stateProperty()); } public void initialize() { @@ -108,6 +133,26 @@ public class UpdatesPreferencesController implements FxController { environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); } + @FXML + public void prepareUpdate() { + if (updatePreparationTask.isDone()) { + try { + // TODO: check if all vaults closed? + var restartProcess = updatePreparationTask.get().applyUpdate(); + assert restartProcess.isAlive(); + Platform.exit(); // TODO: prompt? + } catch (IOException | InterruptedException | ExecutionException e) { + LOG.error("Oh no", e); // TODO: Show error dialog + } + } else if (updatePreparationTask.isRunning()) { + throw new IllegalStateException("Update already in progress"); + } else if (updatePreparationTask.isCancelled()) { + throw new IllegalStateException("Update preparation task was cancelled"); + } else { + Thread.startVirtualThread(updatePreparationTask); + } + } + /* Observable Properties */ public ObjectBinding checkForUpdatesButtonStateProperty() { @@ -186,4 +231,21 @@ public class UpdatesPreferencesController implements FxController { return checkFailed.getValue(); } + public Task getUpdatePreparationTask() { + return updatePreparationTask; + } + + public StringBinding updateButtonTitleProperty() { + return updateButtonTitle; + } + + public String getUpdateButtonTitle() { + return switch (updatePreparationTask.getState()) { + case READY -> "Prepare Update"; // TODO: resourceBundle.getString("preferences.updates.preparingUpdate")... + case SCHEDULED, RUNNING -> "Preparing Update..."; + case SUCCEEDED -> "Restart to Update"; + case FAILED, CANCELLED -> "failed"; + }; + } + } diff --git a/src/main/java/org/cryptomator/updater/UpdateMechanism.java b/src/main/java/org/cryptomator/updater/UpdateMechanism.java index 7a599e529..540633fec 100644 --- a/src/main/java/org/cryptomator/updater/UpdateMechanism.java +++ b/src/main/java/org/cryptomator/updater/UpdateMechanism.java @@ -1,17 +1,15 @@ package org.cryptomator.updater; -import org.cryptomator.integrations.common.IntegrationsLoader; import org.cryptomator.integrations.common.NamedServiceProvider; import org.jetbrains.annotations.Blocking; import javafx.concurrent.Task; import java.io.IOException; -import java.util.stream.Stream; public interface UpdateMechanism extends NamedServiceProvider { - static Stream get() { - return IntegrationsLoader.loadAll(UpdateMechanism.class); + static UpdateMechanism get() { + return new MacOsDmgUpdateMechanism(); // TODO: IntegrationsLoader.load(UpdateMechanism.class).orElseThrow(); } /** @@ -23,7 +21,7 @@ public interface UpdateMechanism extends NamedServiceProvider { /** * Performs as much as possible to prepare the update. This may include downloading the update, checking signatures, etc. - * @return a {@link Task} that can be used to monitor the progress of the update preparation. The task will complete when the preparation is done. + * @return a new {@link Task} that can be used to monitor the progress of the update preparation. The task will complete when the preparation is done. * @throws IOException I/O error during preparation, such as network issues or file access problems. */ UpdateProcess prepareUpdate() throws IOException; // TODO: exception types? diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index d0910949b..264ee7597 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -14,6 +14,7 @@ + + + +