cleanup + error handling

This commit is contained in:
Sebastian Stenzel
2025-11-06 15:35:12 +01:00
parent 9e4006cc89
commit 8a243a01aa
13 changed files with 59 additions and 38 deletions

View File

@@ -7,7 +7,7 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.updater.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -3,7 +3,7 @@ package org.cryptomator.ui.preferences;
import com.google.common.io.CharStreams;
import org.cryptomator.common.Environment;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.updater.UpdateChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -2,7 +2,7 @@ package org.cryptomator.ui.preferences;
import org.cryptomator.common.Environment;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.updater.UpdateChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -6,7 +6,7 @@ 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.UpdateChecker;
import org.cryptomator.updater.FallbackUpdateInfo;
import org.cryptomator.updater.UpdateService;
import org.slf4j.Logger;
@@ -22,7 +22,10 @@ import javafx.beans.binding.BooleanExpression;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
@@ -60,9 +63,9 @@ public class UpdatesPreferencesController implements FxController {
private final ObjectBinding<ContentDisplay> updateButtonState;
private final ObservableValue<String> timeDifferenceMessage;
private final StringBinding lastUpdateCheckMessage;
private final BooleanBinding prohibitUpdateWhileUnlocked;
private final BooleanBinding updateButtonDisabled;
private final StringProperty errorMessage = new SimpleStringProperty("");
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
/* FXML */
@@ -76,6 +79,7 @@ public class UpdatesPreferencesController implements FxController {
this.settings = settings;
this.updateChecker = updateChecker;
this.updateService = new UpdateService(updateChecker.updateProperty());
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
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());
@@ -83,7 +87,6 @@ public class UpdatesPreferencesController implements FxController {
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.updateProperty());
this.updateButtonDisabled = Bindings.when(worker.isEqualTo(updateChecker)).then(running).otherwise(prohibitUpdateWhileUnlocked.or(running));
}
@@ -98,6 +101,7 @@ public class UpdatesPreferencesController implements FxController {
delay.play();
}
});
updateChecker.setOnFailed(this::checkFailed);
updateService.setOnSucceeded(this::updateSucceeded);
updateService.setOnFailed(this::updateFailed);
}
@@ -119,6 +123,12 @@ public class UpdatesPreferencesController implements FxController {
}
}
private void checkFailed(WorkerStateEvent workerStateEvent) {
assert workerStateEvent.getSource() == updateChecker;
LOG.error("Update check failed.", updateChecker.getException());
errorMessage.set(resourceBundle.getString("preferences.updates.checkFailed"));
}
private void updateSucceeded(WorkerStateEvent workerStateEvent) {
assert workerStateEvent.getSource() == updateService;
var lastStep = updateService.getValue();
@@ -138,9 +148,10 @@ public class UpdatesPreferencesController implements FxController {
private void updateFailed(WorkerStateEvent workerStateEvent) {
assert workerStateEvent.getSource() == updateService;
LOG.error("Update failed.", updateService.getException());
updateService.reset();
errorMessage.set(resourceBundle.getString("preferences.updates.updateFailed"));
// try fallback mechanism:
updateChecker.recheckWithFallbackMechanism();
updateService.reset();
}
@FXML
@@ -180,9 +191,9 @@ public class UpdatesPreferencesController implements FxController {
} else {
return switch (updateService.getState()) {
case READY -> updateChecker.getUpdate().updateMechanism().getName();
case SCHEDULED, RUNNING -> updateService.getMessage(); // "Preparing Update..."; // TODO: resourceBundle.getString("preferences.updates.preparingUpdate")...
case SUCCEEDED -> "Restart to Update"; // TODO: resourceBundle.getString("preferences.updates.readyToRestart")...
case FAILED, CANCELLED -> "failed";
case SCHEDULED, RUNNING -> updateService.getMessage();
case SUCCEEDED -> resourceBundle.getString("generic.button.done");
case FAILED, CANCELLED -> "failed"; // should never be visible
};
}
}
@@ -233,6 +244,14 @@ public class UpdatesPreferencesController implements FxController {
}
}
public String getErrorMessage() {
return errorMessage.get();
}
public ReadOnlyStringProperty errorMessageProperty() {
return errorMessage;
}
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.getUpdate());

View File

@@ -2,7 +2,7 @@ package org.cryptomator.ui.updatereminder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.updater.UpdateChecker;
import javax.inject.Inject;
import javafx.fxml.FXML;

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.fxapp;
package org.cryptomator.updater;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

View File

@@ -5,15 +5,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.integrations.common.DisplayName;
import org.cryptomator.integrations.common.Priority;
import org.cryptomator.integrations.update.BasicUpdateInfo;
import org.cryptomator.integrations.update.UpdateInfo;
import org.cryptomator.integrations.common.LocalizedDisplayName;
import org.cryptomator.integrations.update.UpdateMechanism;
import org.cryptomator.integrations.update.UpdateStep;
import org.cryptomator.integrations.update.UpdateStepAdapter;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,11 +23,9 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped
@Priority(Priority.FALLBACK)
@DisplayName("Show Download Page") // TODO localize
@LocalizedDisplayName(bundle = "i18n.strings", key = "preferences.updates.visitDownloadPage")
public class FallbackUpdateMechanism implements UpdateMechanism<FallbackUpdateInfo> {
private static final Logger LOG = LoggerFactory.getLogger(FallbackUpdateMechanism.class);

View File

@@ -110,8 +110,7 @@ public class MacOsDmgUpdateMechanism extends DownloadUpdateMechanism {
} else if (selfPath.contains("/Cryptomator.app/")) {
installPath = selfPath.substring(0, selfPath.indexOf("/Cryptomator.app/")) + "/Cryptomator.app";
} else {
installPath = "/Applications/Cryptomator.app";
// throw new UpdateFailedException("Cannot determine destination path for Cryptomator.app, current path: " + selfPath);
throw new UpdateFailedException("Cannot determine destination path for Cryptomator.app, current path: " + selfPath);
}
LOG.info("Restarting to apply Update in {} now...", workDir);
String script = """

View File

@@ -1,11 +1,11 @@
package org.cryptomator.ui.fxapp;
package org.cryptomator.updater;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.update.UpdateFailedException;
import org.cryptomator.integrations.update.UpdateInfo;
import org.cryptomator.integrations.update.UpdateMechanism;
import org.cryptomator.updater.FallbackUpdateMechanism;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,7 +19,6 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
import java.net.http.HttpClient;
import java.time.Instant;
import java.util.concurrent.Executors;
@@ -46,19 +45,16 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
private final BooleanBinding updateAvailable = update.isNotNull();
private final ObjectBinding<UpdateCheckState> updateState = Bindings.createObjectBinding(this::getUpdateCheckState, stateProperty());
private final BooleanBinding checkFailed = Bindings.equal(UpdateCheckState.CHECK_FAILED, updateState);
private final HttpClient httpClient;
private final UpdateMechanism<?> fallbackUpdateMechanism;
private UpdateMechanism<?> updateMechanism;
@Inject
UpdateChecker(Settings settings, //
Environment env,
FallbackUpdateMechanism fallbackUpdateMechanism,
UpdateCheckerHttpClient httpClient) {
FallbackUpdateMechanism fallbackUpdateMechanism) {
this.env = env;
this.settings = settings;
this.lastSuccessfulUpdateCheck = settings.lastSuccessfulUpdateCheck;
this.httpClient = httpClient;
this.fallbackUpdateMechanism = fallbackUpdateMechanism;
// Prefer the safer fallback mechanism if the last update attempt was already made by this app version
@@ -178,7 +174,7 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
@Override
protected UpdateInfo<?> call() {
try {
try (var httpClient = new UpdateCheckerHttpClient(env)) {
var result = updateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
if (result != null) {
return result;
@@ -190,7 +186,7 @@ public class UpdateChecker extends ScheduledService<UpdateInfo<?>> {
return null;
}
LOG.debug("Trying fallback update check...");
try {
try (var httpClient = new UpdateCheckerHttpClient(env)) {
return fallbackUpdateMechanism.checkForUpdate(env.getAppVersion(), httpClient);
} catch (UpdateFailedException e) {
LOG.error("Fallback update check failed.", e);

View File

@@ -1,6 +1,5 @@
package org.cryptomator.ui.fxapp;
package org.cryptomator.updater;
import jakarta.inject.Inject;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
@@ -12,12 +11,10 @@ import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
@FxApplicationScoped
public class UpdateCheckerHttpClient extends DelegatingHttpClient {
private final String userAgent;
@Inject
public UpdateCheckerHttpClient(Environment env) {
var delegate = HttpClient.newBuilder() //
.followRedirects(HttpClient.Redirect.NORMAL) // from version 1.6.11 onwards, Cryptomator can follow redirects, in case this URL ever changes

View File

@@ -4,6 +4,8 @@ import org.cryptomator.integrations.update.UpdateInfo;
import org.cryptomator.integrations.update.UpdateMechanism;
import org.cryptomator.integrations.update.UpdateStep;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
@@ -18,6 +20,8 @@ import java.util.concurrent.TimeUnit;
*/
public class UpdateService extends Service<UpdateStep> {
private final BooleanBinding updateFailed = Bindings.equal(State.FAILED, stateProperty());
private ObservableValue<UpdateInfo<?>> updateInfo;
public UpdateService(ObservableValue<UpdateInfo<?>> updateInfo) {
@@ -65,4 +69,13 @@ public class UpdateService extends Service<UpdateStep> {
}
}
/* Observable Properties */
public boolean isUpdateFailed() {
return updateFailed.get();
}
public BooleanBinding updateFailedProperty() {
return updateFailed;
}
}

View File

@@ -52,18 +52,20 @@
<Text text="%preferences.updates.prohibitedDueToUnlockedVaults.3"/>
</TextFlow>
<TextFlow styleClass="text-flow" textAlignment="CENTER" visible="${controller.updateChecker.checkFailed}" managed="${controller.updateChecker.checkFailed}">
<TextFlow styleClass="text-flow" textAlignment="CENTER" visible="${!controller.errorMessage.empty}" managed="${!controller.errorMessage.empty}">
<FontAwesome5IconView glyphSize="12" styleClass="glyph-icon-orange" glyph="EXCLAMATION_TRIANGLE"/>
<Text text=" "/>
<Text text="%preferences.updates.checkFailed"/>
<Text text="${controller.errorMessage}"/>
<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" visible="${!controller.updateChecker.updateAvailable}" managed="${!controller.updateChecker.updateAvailable}">
<tooltip>
<Tooltip text="${controller.lastUpdateCheckMessage}" showDelay="10ms"/>
</tooltip>
</FormattedLabel>
<Label text="%preferences.updates.upToDate" visible="${controller.upToDateLabelVisible}" managed="${controller.upToDateLabelVisible}">
<graphic>
<FontAwesome5IconView glyphSize="12" styleClass="glyph-icon-primary" glyph="CHECK"/>

View File

@@ -334,7 +334,9 @@ 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.updateFailed=Update failed. Please install the update manually.
preferences.updates.upToDate=Cryptomator is up-to-date.
preferences.updates.visitDownloadPage=Visit Download Page
## Contribution
preferences.contribute=Support Us