From 26b69beb870f0920779f688bb2d474c643c75a1a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 6 Nov 2025 10:08:49 +0100 Subject: [PATCH] prevent updates while having unlocked vaults --- .../cryptomator/ui/fxapp/UpdateChecker.java | 31 +++++++------ .../UpdatesPreferencesController.java | 44 +++++++++++++++++-- .../updater/FallbackUpdateInfo.java | 6 +++ .../updater/FallbackUpdateMechanism.java | 8 ++-- .../resources/fxml/preferences_updates.fxml | 14 +++++- src/main/resources/i18n/strings.properties | 3 ++ 6 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/cryptomator/updater/FallbackUpdateInfo.java diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java index 174e67050..a69384e41 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateChecker.java @@ -104,13 +104,6 @@ public class UpdateChecker extends ScheduledService> { return new UpdateCheckTask(); } - @Override - protected void failed() { - super.failed(); - LOG.error("Update check failed.", getException()); - } - - /* Observable Properties */ public String getLatestVersion() { @@ -165,13 +158,25 @@ public class UpdateChecker extends ScheduledService> { private class UpdateCheckTask extends Task> { @Override - protected UpdateInfo call() throws UpdateFailedException { - var result = primaryUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient); - if (result == null && primaryUpdateMechanism != fallbackUpdateMechanism) { - LOG.debug("Primary update mechanism did not find an update. Try fallback update mechanism..."); - result = fallbackUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient); + protected UpdateInfo call() { + try { + var result = primaryUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient); + if (result != null) { + return result; + } + } catch (UpdateFailedException e) { + LOG.error("Primary update check failed.", e); + } + if (primaryUpdateMechanism == fallbackUpdateMechanism) { + return null; + } + LOG.debug("Trying fallback update check..."); + try { + return fallbackUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient); + } catch (UpdateFailedException e) { + LOG.error("Fallback update check failed.", e); + return null; } - return result; } } diff --git a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java index 03e4a333e..129abff3a 100644 --- a/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/UpdatesPreferencesController.java @@ -2,9 +2,12 @@ package org.cryptomator.ui.preferences; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.update.UpdateStep; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.fxapp.UpdateChecker; +import org.cryptomator.updater.FallbackUpdateInfo; import org.cryptomator.updater.UpdateService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,12 +17,14 @@ 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.BooleanExpression; import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; import javafx.concurrent.Worker; import javafx.concurrent.WorkerStateEvent; import javafx.fxml.FXML; @@ -47,31 +52,40 @@ public class UpdatesPreferencesController implements FxController { private final Settings settings; private final UpdateChecker updateChecker; private final UpdateService updateService; + private final ObservableList unlockedVaults; + private final VaultService vaultService; private final ObjectBinding> worker; private final BooleanExpression running; private final StringBinding updateButtonTitle; private final ObjectBinding updateButtonState; private final ObservableValue timeDifferenceMessage; private final StringBinding lastUpdateCheckMessage; + + private final BooleanBinding prohibitUpdateWhileUnlocked; + private final BooleanBinding updateButtonDisabled; private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false); /* FXML */ public CheckBox checkForUpdatesCheckbox; @Inject - UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker) { + UpdatesPreferencesController(Application application, Environment environment, ResourceBundle resourceBundle, Settings settings, UpdateChecker updateChecker, ObservableList vaults, VaultService vaultService) { this.application = application; this.environment = environment; this.resourceBundle = resourceBundle; this.settings = settings; this.updateChecker = updateChecker; this.updateService = new UpdateService(updateChecker.lastValueProperty()); + 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()); this.updateButtonTitle = Bindings.createStringBinding(this::getUpdateButtonTitle, worker, updateService.stateProperty(), updateService.messageProperty()); this.updateButtonState = Bindings.createObjectBinding(this::getUpdateButtonState, updateChecker.stateProperty(), updateService.stateProperty()); 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.updateButtonDisabled = Bindings.when(worker.isEqualTo(updateChecker)).then(running).otherwise(prohibitUpdateWhileUnlocked.or(running)); } public void initialize() { @@ -97,8 +111,10 @@ public class UpdatesPreferencesController implements FxController { public void startWork() { if (worker.get().equals(updateChecker)) { updateChecker.checkForUpdatesNow(); + } else if (!unlockedVaults.isEmpty()) { + LOG.warn("Cannot start update due to unlocked vaults."); } else if (worker.get().equals(updateService)) { - // TODO: only allow starting if all vaults are locked; show info label beneath button otherwise + LOG.info("User started update to version {}", updateChecker.getLastValue().version()); updateService.start(); } } @@ -122,9 +138,15 @@ 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? updateService.reset(); } + @FXML + public void lockAllGracefully() { + vaultService.lockAll(unlockedVaults, false); + } + /* Observable Properties */ public UpdateChecker getUpdateChecker() { @@ -197,7 +219,6 @@ public class UpdatesPreferencesController implements FxController { } } - public StringBinding lastUpdateCheckMessageProperty() { return lastUpdateCheckMessage; } @@ -211,6 +232,23 @@ 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()); + } + + public BooleanBinding prohibitUpdateWhileUnlockedProperty() { + return prohibitUpdateWhileUnlocked; + } + + public boolean isUpdateButtonDisabled() { + return updateButtonDisabled.get(); + } + + public BooleanBinding updateButtonDisabledProperty() { + return updateButtonDisabled; + } + public BooleanProperty upToDateLabelVisibleProperty() { return upToDateLabelVisible; } diff --git a/src/main/java/org/cryptomator/updater/FallbackUpdateInfo.java b/src/main/java/org/cryptomator/updater/FallbackUpdateInfo.java new file mode 100644 index 000000000..b0f5d83b0 --- /dev/null +++ b/src/main/java/org/cryptomator/updater/FallbackUpdateInfo.java @@ -0,0 +1,6 @@ +package org.cryptomator.updater; + +import org.cryptomator.integrations.update.UpdateInfo; +import org.cryptomator.integrations.update.UpdateMechanism; + +public record FallbackUpdateInfo(String version, UpdateMechanism updateMechanism) implements UpdateInfo {} diff --git a/src/main/java/org/cryptomator/updater/FallbackUpdateMechanism.java b/src/main/java/org/cryptomator/updater/FallbackUpdateMechanism.java index c6b63e01d..055a96386 100644 --- a/src/main/java/org/cryptomator/updater/FallbackUpdateMechanism.java +++ b/src/main/java/org/cryptomator/updater/FallbackUpdateMechanism.java @@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit; @FxApplicationScoped @Priority(Priority.FALLBACK) @DisplayName("Show Download Page") // TODO localize -public class FallbackUpdateMechanism implements UpdateMechanism { +public class FallbackUpdateMechanism implements UpdateMechanism { private static final Logger LOG = LoggerFactory.getLogger(FallbackUpdateMechanism.class); private static final String LATEST_VERSION_API_URL = "https://api.cryptomator.org/connect/apps/desktop/latest-version"; @@ -53,7 +53,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism } @Override - public BasicUpdateInfo checkForUpdate(String currentVersion, HttpClient httpClient) { + public FallbackUpdateInfo checkForUpdate(String currentVersion, HttpClient httpClient) { try { HttpRequest request = HttpRequest.newBuilder().uri(URI.create(LATEST_VERSION_API_URL)).build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -63,7 +63,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism var release = MAPPER.readValue(response.body(), LatestVersion.class); var updateVersion = release.versionForCurrentOS(); if (UpdateMechanism.isUpdateAvailable(updateVersion, currentVersion)) { - return new BasicUpdateInfo(updateVersion, this); + return new FallbackUpdateInfo(updateVersion, this); } else { return null; } @@ -78,7 +78,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism } @Override - public UpdateStep firstStep(BasicUpdateInfo updateInfo) { + public UpdateStep firstStep(FallbackUpdateInfo updateInfo) { return UpdateStep.of("Go to download page", this::openDownloadPage); // TODO localize } diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index 8689d0506..8be624717 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -27,7 +27,7 @@ - + + + + + + + + + + @@ -49,7 +59,7 @@ - + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 3745b7c07..c17847abd 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -330,6 +330,9 @@ preferences.updates.lastUpdateCheck.never=never preferences.updates.lastUpdateCheck.recently=recently preferences.updates.lastUpdateCheck.daysAgo=%s days ago preferences.updates.lastUpdateCheck.hoursAgo=%s hours ago +preferences.updates.prohibitedDueToUnlockedVaults.1=Please +preferences.updates.prohibitedDueToUnlockedVaults.2=lock your vaults +preferences.updates.prohibitedDueToUnlockedVaults.3=to install the update. preferences.updates.checkFailed=Looking for updates failed. Please check your internet connection or try again later. preferences.updates.upToDate=Cryptomator is up-to-date.