-
Notifications
You must be signed in to change notification settings - Fork 4
Update Mechanism #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Update Mechanism #72
Changes from 23 commits
88f7231
a501a2b
6af0aa8
a2a6f98
f3f3c35
5f29005
28680db
eaa63e5
84076df
e875adb
dde78a1
0f765d6
5dadcbe
61de9f3
ffc3666
2493753
b0d9fe4
41d8e4c
a052dd0
5856f28
6e3bb15
4dbfb11
d75729e
73332c8
a522f36
0fa2e52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -6,11 +6,15 @@ | |||
| import org.cryptomator.integrations.keychain.KeychainAccessProvider; | ||||
| import org.cryptomator.integrations.tray.TrayIntegrationProvider; | ||||
| import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; | ||||
| import org.cryptomator.integrations.update.UpdateMechanism; | ||||
|
|
||||
|
|
||||
| module org.cryptomator.integrations.api { | ||||
| requires static org.jetbrains.annotations; | ||||
| requires org.slf4j; | ||||
| requires com.fasterxml.jackson.databind; | ||||
| requires com.fasterxml.jackson.datatype.jsr310; | ||||
|
||||
| requires com.fasterxml.jackson.datatype.jsr310; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 73332c8
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package org.cryptomator.integrations; | ||
|
|
||
| import java.util.ResourceBundle; | ||
|
|
||
| public enum Localization { | ||
| INSTANCE; | ||
|
|
||
| private final ResourceBundle resourceBundle = ResourceBundle.getBundle("IntegrationsApi"); | ||
|
|
||
| public static ResourceBundle get() { | ||
| return INSTANCE.resourceBundle; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package org.cryptomator.integrations.update; | ||
|
|
||
| public record DownloadUpdateInfo( | ||
| DownloadUpdateMechanism updateMechanism, | ||
| String version, | ||
| DownloadUpdateMechanism.Asset asset | ||
| ) implements UpdateInfo<DownloadUpdateInfo> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| package org.cryptomator.integrations.update; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import org.jetbrains.annotations.Blocking; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.net.URI; | ||
| import java.net.http.HttpClient; | ||
| import java.net.http.HttpRequest; | ||
| import java.net.http.HttpResponse; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.HexFormat; | ||
| import java.util.List; | ||
|
|
||
| public abstract class DownloadUpdateMechanism implements UpdateMechanism<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(); | ||
|
|
||
| @Override | ||
| 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()); | ||
| if (response.statusCode() != 200) { | ||
| LOG.warn("Failed to fetch release: HTTP {}", response.statusCode()); | ||
| return null; | ||
| } | ||
| var release = MAPPER.readValue(response.body(), LatestVersionResponse.class); | ||
| return checkForUpdate(currentVersion, release); | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| LOG.debug("Update check interrupted."); | ||
| return null; | ||
| } catch (IOException e) { | ||
| LOG.warn("Update check failed", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns the first step to prepare the update. This downloads the {@link DownloadUpdateInfo#asset() asset} to a temporary location and verifies its checksum. | ||
| * @param updateInfo The {@link DownloadUpdateInfo} retrieved from {@link #checkForUpdate(String, HttpClient)}. | ||
| * @return a new {@link UpdateStep} that can be used to monitor the download progress. | ||
| * @throws UpdateFailedException When failing to prepare a temporary download location. | ||
| */ | ||
| @Override | ||
| public UpdateStep firstStep(DownloadUpdateInfo updateInfo) throws UpdateFailedException { | ||
| try { | ||
| Path workDir = Files.createTempDirectory("cryptomator-update"); | ||
| return new FirstStep(workDir, updateInfo); | ||
| } catch (IOException e) { | ||
| throw new UpdateFailedException("Failed to create temporary directory for update", e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Second step that is executed after the download has completed in the {@link #firstStep(DownloadUpdateInfo) first step}. | ||
| * @param workDir A temporary working directory to which the asset has been downloaded. | ||
| * @param assetPath The path of the downloaded asset. | ||
| * @param updateInfo The {@link DownloadUpdateInfo} representing the update. | ||
| * @return The next step of the update process. | ||
| * @throws IllegalStateException if preconditions aren't met. | ||
| * @throws IOException indicating an error preventing the next step from starting. | ||
| * @implSpec The returned {@link UpdateStep} must either be stateless or a new instance must be returned on each call. | ||
| */ | ||
| public abstract UpdateStep secondStep(Path workDir, Path assetPath, DownloadUpdateInfo updateInfo) throws IllegalStateException, IOException; | ||
|
|
||
| @Nullable | ||
| @Blocking | ||
| protected abstract DownloadUpdateInfo checkForUpdate(String currentVersion, LatestVersionResponse response); | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public record LatestVersionResponse( | ||
| @JsonProperty("latestVersion") LatestVersion latestVersion, | ||
| @JsonProperty("assets") List<Asset> assets | ||
| ) {} | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public record LatestVersion( | ||
| @JsonProperty("mac") String macVersion, | ||
| @JsonProperty("win") String winVersion, | ||
| @JsonProperty("linux") String linuxVersion | ||
| ) {} | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public record Asset( | ||
| @JsonProperty("name") String name, | ||
| @JsonProperty("digest") String digest, | ||
| @JsonProperty("size") long size, | ||
| @JsonProperty("downloadUrl") String downloadUrl | ||
| ) {} | ||
|
|
||
| private class FirstStep extends DownloadUpdateStep { | ||
| private final Path workDir; | ||
| private final DownloadUpdateInfo updateInfo; | ||
|
|
||
| public FirstStep(Path workDir, DownloadUpdateInfo updateInfo) { | ||
| var uri = URI.create(updateInfo.asset().downloadUrl); | ||
| var destination = workDir.resolve(updateInfo.asset().name); | ||
| var digest = updateInfo.asset().digest().startsWith("sha256:") | ||
| ? HexFormat.of().withLowerCase().parseHex(updateInfo.asset().digest.substring(7)) // remove "sha256:" prefix | ||
| : null; | ||
| var size = updateInfo.asset().size; | ||
| super(uri, destination, digest, size); | ||
| this.workDir = workDir; | ||
| this.updateInfo = updateInfo; | ||
| } | ||
overheadhunter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| public @Nullable UpdateStep nextStep() throws IllegalStateException, IOException { | ||
| if (!isDone()) { | ||
| throw new IllegalStateException("Download not yet completed."); | ||
| } else if (downloadException != null) { | ||
| throw new UpdateFailedException("Download failed.", downloadException); | ||
| } | ||
| return secondStep(workDir, destination, updateInfo); | ||
| } | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.