mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 20:21:27 +00:00
cleanup updater
This commit is contained in:
@@ -17,7 +17,6 @@ import javafx.beans.binding.StringExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.util.Duration;
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Instant;
|
||||
@@ -106,6 +105,10 @@ public class UpdateChecker extends ScheduledService<UpdateInfo> {
|
||||
|
||||
/* Observable Properties */
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion.get();
|
||||
}
|
||||
|
||||
public StringExpression latestVersionProperty() {
|
||||
return latestVersion;
|
||||
}
|
||||
@@ -118,10 +121,18 @@ public class UpdateChecker extends ScheduledService<UpdateInfo> {
|
||||
return updateAvailable;
|
||||
}
|
||||
|
||||
public boolean isCheckFailed() {
|
||||
return checkFailed.get();
|
||||
}
|
||||
|
||||
public BooleanBinding checkFailedProperty() {
|
||||
return checkFailed;
|
||||
}
|
||||
|
||||
public Instant getLastSuccessfulUpdateCheck() {
|
||||
return lastSuccessfulUpdateCheck.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Instant> lastSuccessfulUpdateCheckProperty() {
|
||||
return lastSuccessfulUpdateCheck;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.cryptomator.ui.preferences;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.integrations.update.UpdateInfo;
|
||||
import org.cryptomator.integrations.update.UpdateStep;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||
@@ -16,11 +15,9 @@ 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.binding.StringExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -43,27 +40,21 @@ import java.util.ResourceBundle;
|
||||
public class UpdatesPreferencesController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpdatesPreferencesController.class);
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Settings settings;
|
||||
private final UpdateChecker updateChecker;
|
||||
private final ObjectBinding<ContentDisplay> updateButtonState;
|
||||
private final StringExpression latestVersion;
|
||||
private final ObservableValue<Instant> lastSuccessfulUpdateCheck;
|
||||
private final StringBinding lastUpdateCheckMessage;
|
||||
private final ObservableValue<String> timeDifferenceMessage;
|
||||
private final String currentVersion;
|
||||
private final BooleanBinding checkFailed;
|
||||
private final BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
||||
private final DateTimeFormatter formatter;
|
||||
private final BooleanBinding upToDate;
|
||||
private final UpdateService updateService;
|
||||
private final StringBinding updateButtonTitle;
|
||||
|
||||
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 BooleanProperty upToDateLabelVisible = new SimpleBooleanProperty(false);
|
||||
|
||||
/* FXML */
|
||||
public CheckBox checkForUpdatesCheckbox;
|
||||
@@ -75,39 +66,27 @@ public class UpdatesPreferencesController implements FxController {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.settings = settings;
|
||||
this.updateChecker = updateChecker;
|
||||
|
||||
this.latestVersion = updateChecker.latestVersionProperty();
|
||||
this.lastSuccessfulUpdateCheck = updateChecker.lastSuccessfulUpdateCheckProperty();
|
||||
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, lastSuccessfulUpdateCheck);
|
||||
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, lastSuccessfulUpdateCheck);
|
||||
|
||||
this.currentVersion = updateChecker.getCurrentVersion();
|
||||
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();
|
||||
|
||||
this.updateService = new UpdateService(updateChecker.lastValueProperty().map(UpdateInfo::updateMechanism));
|
||||
this.worker = Bindings.when(updateChecker.updateAvailableProperty()).<Worker<?>>then(updateService).otherwise(updateChecker);
|
||||
this.updateService = new UpdateService(updateChecker.lastValueProperty());
|
||||
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());
|
||||
|
||||
updateChecker.updateAvailableProperty().addListener((_, _, newVal) -> LOG.info("Update available: {}", newVal));
|
||||
|
||||
updateService.setOnSucceeded(this::updateSucceeded);
|
||||
updateService.setOnFailed(this::updateFailed);
|
||||
this.timeDifferenceMessage = Bindings.createStringBinding(this::getTimeDifferenceMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
|
||||
this.lastUpdateCheckMessage = Bindings.createStringBinding(this::getLastUpdateCheckMessage, updateChecker.lastSuccessfulUpdateCheckProperty());
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates);
|
||||
upToDate.addListener((_, _, newVal) -> {
|
||||
if (newVal) {
|
||||
updateChecker.updateAvailableProperty().addListener((_, _, hasUpdate) -> {
|
||||
if (!hasUpdate) {
|
||||
upToDateLabelVisible.set(true);
|
||||
PauseTransition delay = new PauseTransition(javafx.util.Duration.seconds(5));
|
||||
delay.setOnFinished(_ -> upToDateLabelVisible.set(false));
|
||||
delay.play();
|
||||
}
|
||||
});
|
||||
updateService.setOnSucceeded(this::updateSucceeded);
|
||||
updateService.setOnFailed(this::updateFailed);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -158,26 +137,14 @@ public class UpdatesPreferencesController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
public StringExpression latestVersionProperty() {
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion.get();
|
||||
}
|
||||
|
||||
public String getCurrentVersion() {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public StringBinding lastUpdateCheckMessageProperty() {
|
||||
return lastUpdateCheckMessage;
|
||||
}
|
||||
|
||||
public String getLastUpdateCheckMessage() {
|
||||
Instant lastCheck = lastSuccessfulUpdateCheck.getValue();
|
||||
Instant lastCheck = updateChecker.getLastSuccessfulUpdateCheck();
|
||||
if (lastCheck != null && !lastCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
||||
return formatter.format(LocalDateTime.ofInstant(lastCheck, ZoneId.systemDefault()));
|
||||
return FORMATTER.format(LocalDateTime.ofInstant(lastCheck, ZoneId.systemDefault()));
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
@@ -188,7 +155,7 @@ public class UpdatesPreferencesController implements FxController {
|
||||
}
|
||||
|
||||
public String getTimeDifferenceMessage() {
|
||||
var lastSuccessCheck = lastSuccessfulUpdateCheck.getValue();
|
||||
var lastSuccessCheck = updateChecker.getLastSuccessfulUpdateCheck();
|
||||
var duration = Duration.between(lastSuccessCheck, Instant.now());
|
||||
var hours = duration.toHours();
|
||||
if (lastSuccessCheck.equals(Settings.DEFAULT_TIMESTAMP)) {
|
||||
@@ -210,14 +177,6 @@ public class UpdatesPreferencesController implements FxController {
|
||||
return upToDateLabelVisible.get();
|
||||
}
|
||||
|
||||
public BooleanBinding checkFailedProperty() {
|
||||
return checkFailed;
|
||||
}
|
||||
|
||||
public boolean isCheckFailed() {
|
||||
return checkFailed.getValue();
|
||||
}
|
||||
|
||||
public ObjectBinding<Worker<?>> workerProperty() {
|
||||
return worker;
|
||||
}
|
||||
|
||||
@@ -18,14 +18,20 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DownloadUpdateMechanism implements UpdateMechanism {
|
||||
public abstract class DownloadUpdateMechanism implements UpdateMechanism<DownloadUpdateMechanism.DownloadUpdateInfo> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory .getLogger(DownloadUpdateMechanism.class);
|
||||
private static final String LATEST_VERSION_API_URL = "https://api.cryptomator.org/connect/apps/desktop/latest-version?format=1";
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public record DownloadUpdateInfo(
|
||||
DownloadUpdateMechanism updateMechanism,
|
||||
String version,
|
||||
Asset asset
|
||||
) implements UpdateInfo {}
|
||||
|
||||
@Override
|
||||
public UpdateInfo checkForUpdate(String currentVersion, HttpClient httpClient) {
|
||||
public DownloadUpdateInfo 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());
|
||||
@@ -46,7 +52,7 @@ public abstract class DownloadUpdateMechanism implements UpdateMechanism {
|
||||
|
||||
@Nullable
|
||||
@Blocking
|
||||
abstract UpdateInfo checkForUpdate(String currentVersion, LatestVersionResponse response);
|
||||
abstract DownloadUpdateInfo checkForUpdate(String currentVersion, LatestVersionResponse response);
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record LatestVersionResponse(
|
||||
|
||||
@@ -32,7 +32,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<UpdateInfo> {
|
||||
|
||||
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";
|
||||
@@ -62,7 +62,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism {
|
||||
var release = MAPPER.readValue(response.body(), LatestVersion.class);
|
||||
var updateVersion = release.versionForCurrentOS();
|
||||
if (UpdateMechanism.isUpdateAvailable(currentVersion, updateVersion)) {
|
||||
return new UpdateInfo(updateVersion, this);
|
||||
return UpdateInfo.of(updateVersion, this);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class FallbackUpdateMechanism implements UpdateMechanism {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateStep firstStep() {
|
||||
public UpdateStep firstStep(UpdateInfo updateInfo) {
|
||||
return UpdateStep.of("Go to download page", this::openDownloadPage); // TODO localize
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.cryptomator.integrations.common.OperatingSystem;
|
||||
import org.cryptomator.integrations.common.Priority;
|
||||
import org.cryptomator.integrations.update.DownloadUpdateStep;
|
||||
import org.cryptomator.integrations.update.UpdateFailedException;
|
||||
import org.cryptomator.integrations.update.UpdateInfo;
|
||||
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||
import org.cryptomator.integrations.update.UpdateStep;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -15,8 +14,10 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -30,24 +31,25 @@ public class MacOsDmgUpdateMechanism extends DownloadUpdateMechanism {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MacOsDmgUpdateMechanism.class);
|
||||
|
||||
@Override
|
||||
UpdateInfo checkForUpdate(String currentVersion, LatestVersionResponse response) {
|
||||
DownloadUpdateInfo checkForUpdate(String currentVersion, LatestVersionResponse response) {
|
||||
String suffix = switch (System.getProperty("os.arch")) {
|
||||
case "aarch64", "arm64" -> "arm64.dmg";
|
||||
default -> "x64.dmg";
|
||||
};
|
||||
if (UpdateMechanism.isUpdateAvailable(response.latestVersion().macVersion(), currentVersion)
|
||||
&& response.assets().stream().map(Asset::name).anyMatch(s -> s.endsWith(suffix))) {
|
||||
return new UpdateInfo(response.latestVersion().macVersion(), this);
|
||||
var updateVersion = response.latestVersion().macVersion();
|
||||
var asset = response.assets().stream().filter(a -> a.name().endsWith(suffix)).findAny().orElse(null);
|
||||
if (UpdateMechanism.isUpdateAvailable(updateVersion, currentVersion) && asset != null) {
|
||||
return new DownloadUpdateMechanism.DownloadUpdateInfo(this, updateVersion, asset);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateStep firstStep() throws UpdateFailedException {
|
||||
public UpdateStep firstStep(DownloadUpdateInfo updateInfo) throws UpdateFailedException {
|
||||
try {
|
||||
Path workDir = Files.createTempDirectory("cryptomator-update");
|
||||
return new UpdateProcessImpl(workDir);
|
||||
return new UpdateProcessImpl(workDir, updateInfo);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateFailedException("Failed to create temporary directory for update", e);
|
||||
}
|
||||
@@ -55,14 +57,13 @@ public class MacOsDmgUpdateMechanism extends DownloadUpdateMechanism {
|
||||
|
||||
private static class UpdateProcessImpl extends DownloadUpdateStep {
|
||||
|
||||
// FIXME: use URI and CHECKSUM from update API
|
||||
private static final URI UPDATE_URI = URI.create("https://github.com/cryptomator/cryptomator/releases/download/1.17.0/Cryptomator-1.17.0-arm64.dmg");
|
||||
private static final byte[] CHECKSUM = HexFormat.of().withLowerCase().parseHex("03f45e203204e93b39925cbb04e19c9316da4f77debaba4fb5071f0ec8e727e8");
|
||||
private final Path workDir;
|
||||
|
||||
public UpdateProcessImpl(Path workDir) {
|
||||
public UpdateProcessImpl(Path workDir, DownloadUpdateInfo updateInfo) {
|
||||
var destination = workDir.resolve("update.dmg");
|
||||
super(UPDATE_URI, destination, CHECKSUM, 60_000_000L); // initially assume 60 MB for the update size
|
||||
var downloadUri = URI.create(updateInfo.asset().downloadUrl());
|
||||
var checksum = HexFormat.of().withLowerCase().parseHex(updateInfo.asset().digest().substring(7)); // remove "sha256:" prefix
|
||||
super(downloadUri, destination, checksum, 60_000_000L); // initially assume 60 MB for the update size
|
||||
this.workDir = workDir;
|
||||
}
|
||||
|
||||
@@ -111,12 +112,18 @@ public class MacOsDmgUpdateMechanism extends DownloadUpdateMechanism {
|
||||
installPath = "/Applications/Cryptomator.app";
|
||||
// throw new UpdateFailedException("Cannot determine destination path for Cryptomator.app, current path: " + selfPath);
|
||||
}
|
||||
LOG.info("Restarting to apply Update in {} now...", workDir);
|
||||
String script = """
|
||||
while kill -0 ${CRYPTOMATOR_PID} 2> /dev/null; do sleep 0.5; done;
|
||||
cp -R 'Cryptomator.app' "${CRYPTOMATOR_INSTALL_PATH}";
|
||||
open -a "${CRYPTOMATOR_INSTALL_PATH}"
|
||||
if [ -d "${CRYPTOMATOR_INSTALL_PATH}" ]; then
|
||||
echo "Removing old installation at ${CRYPTOMATOR_INSTALL_PATH}";
|
||||
rm -rf "${CRYPTOMATOR_INSTALL_PATH}"
|
||||
fi
|
||||
mv 'Cryptomator.app' "${CRYPTOMATOR_INSTALL_PATH}";
|
||||
open -a "${CRYPTOMATOR_INSTALL_PATH}";
|
||||
""";
|
||||
var command = List.of("bash", "-c", "nohup bash -c \"" + script + "\" >/Users/sebastian/Downloads/nohup.out 2>&1 &");
|
||||
Files.writeString(workDir.resolve("install.sh"), script, StandardCharsets.US_ASCII, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
var command = List.of("bash", "-c", "nohup bash install.sh >install.log 2>&1 &");
|
||||
var processBuilder = new ProcessBuilder(command);
|
||||
processBuilder.directory(workDir.toFile());
|
||||
processBuilder.environment().put("CRYPTOMATOR_PID", String.valueOf(ProcessHandle.current().pid()));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.updater;
|
||||
|
||||
import org.cryptomator.integrations.update.UpdateInfo;
|
||||
import org.cryptomator.integrations.update.UpdateMechanism;
|
||||
import org.cryptomator.integrations.update.UpdateStep;
|
||||
|
||||
@@ -17,30 +18,30 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public class UpdateService extends Service<UpdateStep> {
|
||||
|
||||
private ObservableValue<UpdateMechanism> updateMechanism;
|
||||
private ObservableValue<UpdateInfo> updateInfo;
|
||||
|
||||
public UpdateService(ObservableValue<UpdateMechanism> updateMechanism) {
|
||||
public UpdateService(ObservableValue<UpdateInfo> updateInfo) {
|
||||
setExecutor(Executors.newVirtualThreadPerTaskExecutor());
|
||||
this.updateMechanism = updateMechanism;
|
||||
this.updateInfo = updateInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<UpdateStep> createTask() {
|
||||
return new RunAllStepsTask(updateMechanism.getValue());
|
||||
return new RunAllStepsTask(updateInfo.getValue());
|
||||
}
|
||||
|
||||
private static class RunAllStepsTask extends Task<UpdateStep> {
|
||||
|
||||
private final UpdateMechanism updateMechanism;
|
||||
private final UpdateInfo updateInfo;
|
||||
|
||||
public RunAllStepsTask(UpdateMechanism updateMechanism) {
|
||||
this.updateMechanism = Objects.requireNonNull(updateMechanism);
|
||||
public RunAllStepsTask(UpdateInfo updateInfo) {
|
||||
this.updateInfo = Objects.requireNonNull(updateInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UpdateStep call() throws IOException {
|
||||
try {
|
||||
UpdateStep step = updateMechanism.firstStep();
|
||||
UpdateStep step = updateInfo.updateMechanism().firstStep(updateInfo);
|
||||
UpdateStep lastStep;
|
||||
do {
|
||||
step.start();
|
||||
|
||||
Reference in New Issue
Block a user