Skip to content

External update mechanism #62

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

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from

Conversation

purejava
Copy link
Contributor

No description provided.

Copy link

coderabbitai bot commented Jul 31, 2025

Walkthrough

This change introduces a new update mechanism API within the org.cryptomator.integrations.update package. It adds a module export and service usage for UpdateMechanism in the module descriptor, and requires the java.net.http module. Four new public entities are defined: the UpdateMechanism interface for update integration, the UpdateProcess interface for managing update preparation and application, the DownloadUpdateProcess abstract class for handling asynchronous file downloads with progress tracking and checksum verification, and the UpdateFailedException class for signaling update-related errors. Additionally, a SemVerComparator class is added to compare semantic version strings, along with comprehensive tests for it. All new APIs are marked as experimental.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Complexity arises from the introduction of new public APIs, asynchronous download logic, checksum handling, semantic version comparison, and service loader integration.
  • Review requires attention to concurrency, exception handling, interface design, and correctness of version comparison logic.
  • The changes are contained to a new package and module descriptor, with no modifications to existing business logic.

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dde78a1 and 0f765d6.

📒 Files selected for processing (1)
  • src/test/java/org/cryptomator/integrations/update/SemVerComparatorTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/org/cryptomator/integrations/update/SemVerComparatorTest.java
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (11)
src/main/java/org/cryptomator/integrations/update/UpdateFailedException.java (1)

3-12: Add JavaDoc documentation for better API usability.

The exception class follows standard Java patterns correctly. Consider adding JavaDoc to document when this exception is thrown and its purpose in the update framework.

+/**
+ * Thrown when an update operation fails or is cancelled.
+ * This exception may wrap underlying causes of update failures.
+ */
 public class UpdateFailedException extends Exception {
 
+	/**
+	 * Constructs an UpdateFailedException with the specified detail message.
+	 * 
+	 * @param message the detail message
+	 */
 	public UpdateFailedException(String message) {
 		super(message);
 	}
 
+	/**
+	 * Constructs an UpdateFailedException with the specified detail message and cause.
+	 * 
+	 * @param message the detail message
+	 * @param cause the cause of the exception
+	 */
 	public UpdateFailedException(String message, Throwable cause) {
 		super(message, cause);
 	}
 }
src/main/java/org/cryptomator/integrations/update/ProgressListener.java (1)

3-6: Add JavaDoc documentation for the functional interface.

The functional interface design is clean and follows best practices. Consider adding JavaDoc to document the contract and usage.

+/**
+ * Functional interface for listening to progress updates during update operations.
+ */
 @FunctionalInterface
 public interface ProgressListener {
+	/**
+	 * Called when progress is made during an update operation.
+	 * 
+	 * @param progress the current progress information
+	 */
 	void onProgress(Progress progress);
 }
src/main/java/org/cryptomator/integrations/update/UpdateAvailableListener.java (1)

3-6: Add JavaDoc documentation for consistency with other API interfaces.

The functional interface follows the same clean pattern as other listeners in the package. Consider adding JavaDoc documentation for consistency.

+/**
+ * Functional interface for listening to update availability notifications.
+ */
 @FunctionalInterface
 public interface UpdateAvailableListener {
+	/**
+	 * Called when an update becomes available.
+	 * 
+	 * @param updateAvailable information about the available update
+	 */
 	void onUpdateAvailable(UpdateAvailable updateAvailable);
 }
src/main/java/org/cryptomator/integrations/update/SpawnExitedListener.java (1)

3-6: Complete the documentation set for all listener interfaces.

The functional interface maintains consistency with other listeners in the package. Adding JavaDoc will complete the documentation set for the update API.

+/**
+ * Functional interface for listening to spawned process exit events during updates.
+ */
 @FunctionalInterface
 public interface SpawnExitedListener {
+	/**
+	 * Called when a spawned process exits during the update process.
+	 * 
+	 * @param spawnExited information about the exited process
+	 */
 	void onSpawnExited(SpawnExited spawnExited);
 }
src/main/java/org/cryptomator/integrations/update/UpdateService.java (3)

9-11: Fix typos in class documentation.

There are several spelling errors in the class-level JavaDoc.

- * This is the interface used by Cryptomator to provide a way to update Cryptomator in a convinient way.
- * It's idependent of the supported platforms and package distribution channels.
+ * This is the interface used by Cryptomator to provide a way to update Cryptomator in a convenient way.
+ * It's independent of the supported platforms and package distribution channels.

19-22: Fix typos in method documentation.

Multiple spelling errors in the isSupported() method documentation.

-	 * @return <code>true</code> if this UppdateService can update the app.
+	 * @return <code>true</code> if this UpdateService can update the app.
 	 * @implSpec This method must not throw any exceptions and should fail fast
-	 * returning <code>false</code> if it's not possible to use this UppdateService
+	 * returning <code>false</code> if it's not possible to use this UpdateService

36-36: Fix typo in exception documentation.

Spelling error in the @throws documentation.

-	 * @throws UpdateFailedException If the udpate wasn't successful or was cancelled.
+	 * @throws UpdateFailedException If the update wasn't successful or was cancelled.
src/main/java/org/cryptomator/integrations/update/SpawnStartedListener.java (1)

4-5: Consider adding null safety documentation.

The functional interface design is clean and follows Java conventions. However, consider adding documentation to clarify whether the spawnStarted parameter can be null to prevent potential NPEs in implementations.

 @FunctionalInterface
 public interface SpawnStartedListener {
+	/**
+	 * Called when a spawn process has started.
+	 * @param spawnStarted the spawn information, must not be null
+	 */
 	void onSpawnStarted(SpawnStarted spawnStarted);
 }
src/main/java/org/cryptomator/integrations/update/SpawnStarted.java (1)

3-14: Consider adding equals, hashCode, and toString methods.

Data classes typically benefit from proper equals/hashCode implementation for use in collections and toString for debugging. Also, consider adding documentation to explain what relPid represents.

+/**
+ * Represents information about a spawned process.
+ * @param pid the process ID
+ * @param relPid the relative process ID (explain what this means)
+ */
 public class SpawnStarted {
 	private final long pid;
 	private final long relPid;
 
 	// ... existing code ...
 
 	public long getPid() {  return pid; }
 	public long getRelPid() { return relPid; }
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) return true;
+		if (obj == null || getClass() != obj.getClass()) return false;
+		SpawnStarted that = (SpawnStarted) obj;
+		return pid == that.pid && relPid == that.relPid;
+	}
+
+	@Override
+	public int hashCode() {
+		return Long.hashCode(pid) * 31 + Long.hashCode(relPid);
+	}
+
+	@Override
+	public String toString() {
+		return "SpawnStarted{pid=" + pid + ", relPid=" + relPid + '}';
+	}
 }
src/main/java/org/cryptomator/integrations/update/SpawnExited.java (1)

3-14: Consider adding equals, hashCode, and toString methods.

For consistency with data class best practices and to enable proper usage in collections and debugging scenarios.

+/**
+ * Represents information about a spawned process that has exited.
+ */
 public class SpawnExited {
 	private final long pid;
 	private final long exitStatus;
 
 	// ... existing code ...
 
 	public long getPid() {  return pid; }
 	public long getExitStatus() { return exitStatus; }
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) return true;
+		if (obj == null || getClass() != obj.getClass()) return false;
+		SpawnExited that = (SpawnExited) obj;
+		return pid == that.pid && exitStatus == that.exitStatus;
+	}
+
+	@Override
+	public int hashCode() {
+		return Long.hashCode(pid) * 31 + Long.hashCode(exitStatus);
+	}
+
+	@Override
+	public String toString() {
+		return "SpawnExited{pid=" + pid + ", exitStatus=" + exitStatus + '}';
+	}
 }
src/main/java/org/cryptomator/integrations/update/UpdateAvailable.java (1)

3-23: Consider adding equals, hashCode, toString, and documentation.

For consistency with data class best practices and to provide clarity on what each commit type represents.

+/**
+ * Represents information about available software updates.
+ * Contains commit references for running, local, and remote versions.
+ */
 public class UpdateAvailable {
 	private final String runningCommit;
 	private final String localCommit;
 	private final String remoteCommit;
 
 	// ... existing code ...
 
+	/**
+	 * @return the commit hash of the currently running version
+	 */
 	public String getRunningCommit() {
 		return runningCommit;
 	}
+	/**
+	 * @return the commit hash of the locally available version
+	 */
 	public String getLocalCommit() {
 		return localCommit;
 	}
+	/**
+	 * @return the commit hash of the remote version
+	 */
 	public String getRemoteCommit() {
 		return remoteCommit;
 	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) return true;
+		if (obj == null || getClass() != obj.getClass()) return false;
+		UpdateAvailable that = (UpdateAvailable) obj;
+		return Objects.equals(runningCommit, that.runningCommit) &&
+			   Objects.equals(localCommit, that.localCommit) &&
+			   Objects.equals(remoteCommit, that.remoteCommit);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(runningCommit, localCommit, remoteCommit);
+	}
+
+	@Override
+	public String toString() {
+		return "UpdateAvailable{" +
+			   "runningCommit='" + runningCommit + '\'' +
+			   ", localCommit='" + localCommit + '\'' +
+			   ", remoteCommit='" + remoteCommit + '\'' +
+			   '}';
+	}
 }

You'll need to add this import:

+import java.util.Objects;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a39c129 and 28680db.

📒 Files selected for processing (12)
  • src/main/java/module-info.java (3 hunks)
  • src/main/java/org/cryptomator/integrations/common/DistributionChannel.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/Progress.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/ProgressListener.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/SpawnExited.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/SpawnExitedListener.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/SpawnStarted.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/SpawnStartedListener.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateAvailable.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateAvailableListener.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateFailedException.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateService.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/org/cryptomator/integrations/update/UpdateService.java (1)
src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java (1)
  • IntegrationsLoader (19-163)
🔇 Additional comments (7)
src/main/java/org/cryptomator/integrations/common/DistributionChannel.java (1)

12-41: Well-designed annotation following Java best practices.

The annotation implementation is excellent:

  • Proper retention policy for runtime access
  • Correct use of repeatable annotation pattern
  • Comprehensive enum values covering major distribution channels
  • Appropriate experimental marking for new API
  • Safe default value (UNKNOWN)
src/main/java/module-info.java (6)

9-9: LGTM!

The import statement is correctly added for the new UpdateService.


24-24: LGTM!

The package export is correctly added to make the update integration API accessible.


34-34: LGTM!

The service usage declaration is correctly added to enable service loading of UpdateService implementations.


9-9: LGTM!

The import statement correctly references the new UpdateService interface, following the existing pattern in the module descriptor.


24-24: LGTM!

The export declaration properly exposes the new update package to other modules, maintaining consistency with the existing export structure.


34-34: LGTM!

The uses declaration correctly enables service loading for UpdateService, which is essential for the integration API pattern used throughout this module.

Copy link
Member

@overheadhunter overheadhunter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive overall proof of concept, however I believe the API can and needs to be more generic. After all we want the new API to be a common denominator for basically all update mechanisms.

I imagine a very lean API (as suggested in a separate PoC: UpdateMechanism and UpdateProcess)

@purejava
Copy link
Contributor Author

purejava commented Aug 1, 2025

Impressive overall proof of concept, however I believe the API can and needs to be more generic. After all we want the new API to be a common denominator for basically all update mechanisms.

I imagine a very lean API (as suggested in a separate PoC: UpdateMechanism and UpdateProcess)

Foremost, thanks for the quick review, and thanks for the suggestion on how to change the API to fit together with your PoC - UpdateMechanism and UpdateProcess.

@purejava
Copy link
Contributor Author

purejava commented Aug 2, 2025

Changes to correlate with API as suggested in PoC: UpdateMechanism and UpdateProcess were implemented and tested.

@overheadhunter Tested again with integrations-linux and Cryptomator.

Hopefully, no more changes to integrations-api are needed. 🤞🏻

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
src/main/java/org/cryptomator/integrations/update/UpdateProcess.java (2)

31-40: Clarify await(timeout) return value

The Javadoc states “returns true if the update is prepared” but omits what false means (timeout vs. error vs. cancellation). Document all possible outcomes to avoid misuse.


48-51: Expose consistent failure semantics

applyUpdate() throws IOException, while prepareUpdate() in UpdateMechanism throws the custom UpdateFailedException. For symmetry (and simpler caller handling) consider using the same checked exception type here as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaa63e5 and e875adb.

📒 Files selected for processing (5)
  • src/main/java/module-info.java (3 hunks)
  • src/main/java/org/cryptomator/integrations/update/DownloadUpdateProcess.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateFailedException.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateMechanism.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateProcess.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/cryptomator/integrations/update/UpdateFailedException.java
  • src/main/java/module-info.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/org/cryptomator/integrations/update/UpdateProcess.java (1)
src/main/java/org/cryptomator/integrations/update/UpdateFailedException.java (1)
  • ApiStatus (7-17)

Comment on lines 11 to 13
static UpdateMechanism get() {
return IntegrationsLoader.load(UpdateMechanism.class).orElseThrow(); // Fallback "show download page" mechanism always available.
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

orElseThrow() loses diagnostic context

If no implementation is found the caller gets a naked NoSuchElementException without guidance. Provide a descriptive message or fall back to the “show-download-page” implementation directly.

🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/integrations/update/UpdateMechanism.java around
lines 11 to 13, the use of orElseThrow() without a message causes a generic
NoSuchElementException with no diagnostic context if no implementation is found.
Fix this by either providing a descriptive exception message in orElseThrow() or
by returning a default fallback implementation that shows the download page
directly to improve error clarity and handling.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/main/java/org/cryptomator/integrations/update/DownloadUpdateProcess.java (1)

163-170: EOF handling inverted – negative length fed to MessageDigest
The condition should be n > 0; passing ‑1 to digest.update and counter.add throws IllegalArgumentException and corrupts progress.

-if (n == -1) {
-    digest.update(b, off, n);
-    counter.add(n);
-}
+if (n > 0) {
+    digest.update(b, off, n);
+    counter.add(n);
+}
🧹 Nitpick comments (3)
src/main/java/org/cryptomator/integrations/update/SemVerComparator.java (1)

9-13: Make the comparator non-instantiable and clearly singleton

Since an INSTANCE is provided, prevent additional instantiation and clarify intent.

Apply this diff:

-public class SemVerComparator implements Comparator<String> {
+public final class SemVerComparator implements Comparator<String> {
 
 	public static final SemVerComparator INSTANCE = new SemVerComparator();
+
+	private SemVerComparator() {
+		// singleton
+	}
src/main/java/org/cryptomator/integrations/update/DownloadUpdateProcess.java (2)

48-53: this still captured during construction
Moving the start() call out is an improvement, but the thread capturing this is still created before the object is fully constructed. Consider building/starting the thread in startDownload() instead to avoid premature escape entirely.


55-63: Progress can exceed 1.0
If the final download size differs from the initial estimate, loadedBytes may outrun totalBytes, yielding values > 1. Clamp the result to 1.0 for predictable progress reporting.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e875adb and dde78a1.

📒 Files selected for processing (5)
  • src/main/java/org/cryptomator/integrations/update/DownloadUpdateProcess.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/SemVerComparator.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateMechanism.java (1 hunks)
  • src/main/java/org/cryptomator/integrations/update/UpdateProcess.java (1 hunks)
  • src/test/java/org/cryptomator/integrations/update/SemVerComparatorTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/cryptomator/integrations/update/UpdateMechanism.java
  • src/main/java/org/cryptomator/integrations/update/UpdateProcess.java
🔇 Additional comments (1)
src/test/java/org/cryptomator/integrations/update/SemVerComparatorTest.java (1)

10-75: Good coverage of SemVer precedence, including build metadata and spec’s example

The tests validate equality, ordering, and the spec’s precedence example. Solid baseline.

@cryptomator cryptomator deleted a comment from coderabbitai bot Aug 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants