Add Velopack auto-update support #1
Loading…
Reference in a new issue
No description provided.
Delete branch "f/velopack-updates"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Auto-updates for consumers via WithVelopackUpdates, feeding from repo releases.
🤖 Claude AI Code Review
Code Review: Add Velopack auto-update support
1. Summary
This PR integrates Velopack for auto-updates. It adds:
WithVelopackUpdates(repoUrl)builder methodIUpdateService/UpdateServicepair that checks, downloads, and applies updatesRunApp()BlazorHostWindowThe design is clean, the docs are honest about the security tradeoffs, and the abstraction (
IUpdateService) is well-scoped. Nicely done overall.2. Code Quality
Strengths:
IUpdateServiceis minimal and well-documented, including thread-marshalling warnings onStateChanged.Interlocked.CompareExchangeon_checkInProgressis correct.Minor:
UpdateReadyis a plain auto-property written inside the lock but read without one — see below.CreateSourcecallsnew Uri(repoUrl)without validation, but this is guarded byWithVelopackUpdates'sUri.TryCreate, so it's fine in practice.3. Potential Issues
🔴
UpdateReadyread/write memory visibilityUpdateReadyis written inside_stateLockbut read outside it (e.g., from Blazor circuits). The plain auto-property has no memory barrier on the read path, so a reader thread may observe a stale value indefinitely.Consider
volatilebacking field or reading under the lock:Same concern applies to
PendingVersion => _pendingUpdate?.TargetFullRelease?.Version?.ToString()— it reads_pendingUpdateoutside the lock. The comment claims "guard the trio so a reader never sees a half-published update," but the readers don't actually take the lock, defeating the intent.🟡 Fire-and-forget on
Task.Runswallows nothing but relies on service contractThis is safe only because
CheckAndDownloadAsyncguarantees it never throws. That contract is documented, and the implementation wraps everything in try/catch — good. ButInterlockedreset usesVolatile.Writewhile the finally is reached; if an exception escaped before the try (it can't here), the guard would stick. Acceptable as-is.🟡
VelopackApp.Build().Run()placementVelopack docs recommend running the hook as the very first thing in
Main, before any other framework initialization. Here it runs insideRunApp(), which may execute after other setup depending on the consumer'sMain. For install/update/uninstall hooks that exit early this is usually fine, but document that consumers shouldn't do meaningful work beforeRunApp(), or expose the hook separately. Worth confirming against Velopack's guidance.🟡
GiteaSource/GithubSourcecreated withnulltokenDocumented as a known limitation (no private feeds). Fine, but note that
GithubSource(repoUrl, null, false)— the third argprerelease: false— means prereleases are silently excluded. This is a reasonable default but isn't surfaced in the API or docs.🟢 Duplicate
UpdateManagerconstructionCheckAndDownloadAsynccreates a freshUpdateManagereach call. Minor allocation; not a problem given check frequency.4. Recommendations
UpdateReady/_pendingUpdatereads (volatile or lock the getters). This is the one item I'd want addressed before merge.RunApp()ordering in docs — tell consumers not to run logic before it when updates are enabled, or verify Velopack tolerates the current placement.prereleasesupport (or at least documenting the currentfalse).CheckAndDownloadAsyncshould surface a "check failed" signal so UIs can distinguish "no update" from "couldn't reach feed" — not required, just a UX nicety.5. Verdict
COMMENT (leaning APPROVE)
This is a solid, well-documented feature with good security awareness. The only substantive concern is the memory-visibility bug on
UpdateReady/_pendingUpdate, where the stated locking intent isn't fully realized on the read path. Address that (a one-linevolatilechange) and this is a clean approve.📊 Tokens: 6438 input + 1796 output | 💰 Cost: ~$0.0771
Generated by Claude Opus 4.8 via Gitea Actions