prevent updates while having unlocked vaults

This commit is contained in:
Sebastian Stenzel
2025-11-06 10:08:49 +01:00
parent f95bf87a4b
commit 26b69beb87
6 changed files with 84 additions and 22 deletions

View File

@@ -104,13 +104,6 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
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<UpdateInfo<?>> {
private class UpdateCheckTask extends Task<UpdateInfo<?>> {
@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;
}
}

View File

@@ -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<Vault> unlockedVaults;
private final VaultService vaultService;
private final ObjectBinding<Worker<?>> worker;
private final BooleanExpression running;
private final StringBinding updateButtonTitle;
private final ObjectBinding<ContentDisplay> updateButtonState;
private final ObservableValue<String> 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<Vault> 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()).<Worker<?>>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;
}

View File

@@ -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<FallbackUpdateInfo> updateMechanism) implements UpdateInfo<FallbackUpdateInfo> {}

View File

@@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit;
@FxApplicationScoped
@Priority(Priority.FALLBACK)
@DisplayName("Show Download Page") // TODO localize
public class FallbackUpdateMechanism implements UpdateMechanism<BasicUpdateInfo> {
public class FallbackUpdateMechanism implements UpdateMechanism<FallbackUpdateInfo> {
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<BasicUpdateInfo>
}
@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<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
@@ -63,7 +63,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism<BasicUpdateInfo>
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<BasicUpdateInfo>
}
@Override
public UpdateStep firstStep(BasicUpdateInfo updateInfo) {
public UpdateStep firstStep(FallbackUpdateInfo updateInfo) {
return UpdateStep.of("Go to download page", this::openDownloadPage); // TODO localize
}

View File

@@ -27,7 +27,7 @@
<VBox alignment="CENTER" spacing="12">
<FormattedLabel format="%preferences.updates.updateAvailable" arg1="${controller.updateChecker.latestVersion}" textAlignment="CENTER" wrapText="true" visible="${controller.updateChecker.updateAvailable}"/>
<Button text="${controller.updateButtonTitle}" defaultButton="true" onAction="#startWork" disable="${controller.running}" contentDisplay="${controller.updateButtonState}">
<Button text="${controller.updateButtonTitle}" defaultButton="true" onAction="#startWork" disable="${controller.updateButtonDisabled}" contentDisplay="${controller.updateButtonState}">
<graphic>
<VBox spacing="5" alignment="CENTER">
<ProgressBar maxWidth="200"
@@ -42,6 +42,16 @@
</graphic>
</Button>
<TextFlow styleClass="text-flow" textAlignment="CENTER" visible="${controller.prohibitUpdateWhileUnlocked}" managed="${controller.prohibitUpdateWhileUnlocked}">
<FontAwesome5IconView glyphSize="12" styleClass="glyph-icon-primary" glyph="LOCK_OPEN"/>
<Text text=" "/>
<Text text="%preferences.updates.prohibitedDueToUnlockedVaults.1"/>
<Text text=" "/>
<Hyperlink styleClass="hyperlink-underline" text="%preferences.updates.prohibitedDueToUnlockedVaults.2" onAction="#lockAllGracefully"/>
<Text text=" "/>
<Text text="%preferences.updates.prohibitedDueToUnlockedVaults.3"/>
</TextFlow>
<TextFlow styleClass="text-flow" textAlignment="CENTER" visible="${controller.updateChecker.checkFailed}" managed="${controller.updateChecker.checkFailed}">
<FontAwesome5IconView glyphSize="12" styleClass="glyph-icon-orange" glyph="EXCLAMATION_TRIANGLE"/>
<Text text=" "/>
@@ -49,7 +59,7 @@
<Text text=" "/>
<Hyperlink styleClass="hyperlink-underline" text="%preferences.general.debugDirectory" onAction="#showLogfileDirectory"/>
</TextFlow>
<FormattedLabel format="%preferences.updates.lastUpdateCheck" arg1="${controller.timeDifferenceMessage}" textAlignment="CENTER" wrapText="true">
<FormattedLabel format="%preferences.updates.lastUpdateCheck" arg1="${controller.timeDifferenceMessage}" textAlignment="CENTER" wrapText="true" visible="${!controller.updateChecker.updateAvailable}" managed="${!controller.updateChecker.updateAvailable}">
<tooltip>
<Tooltip text="${controller.lastUpdateCheckMessage}" showDelay="10ms"/>
</tooltip>

View File

@@ -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.