Skip to content

Auto-Update

Section summary A non-disruptive update mechanism sourced from GitHub Releases. The pipeline — check → download → extract → user consent → apply-on-exit → restart — ensures that no actual files are overwritten until the user explicitly consents.

Components

Component Role
UpdateService Handles check, download, extract, and preparing the files to apply
Update notice When an update is ready, shows a centered banner just above the status bar with a Restart button and a dismiss button
Settings (settings.json) AutoUpdateEnabled, LastUpdateCheck, SkippedVersions — check interval, last check time, and list of skipped versions

Check and fetch flow

  1. Automatic check: When AutoUpdateEnabled == true, UpdateService.Initialize() creates a Timer that fires the first check 30 seconds after startup and then every 4 hours. As a rate-limit guard, any check within 30 minutes of the previous one is skipped (guarded by _lastCheckUtc).
  2. Manual check: Control Deck → About → "Check for updates" calls CheckForUpdatesAsync() immediately. The result is surfaced via a toast and the UpdateStatus property.
  3. Two-stage version lookup:
  4. Primary: the Cloudflare Worker (VersionCheckUrl), a cache layer to avoid GitHub API rate limits.
  5. Fallback: if the Worker returns an error property or any HTTP failure occurs, the app automatically falls back to the GitHub API (releases/latest) via CheckGitHubDirectAsync.
  6. Download type priority (when Auto is specified):
  7. Delta: used only when DeltaFromVersion == AppVersion matches.
  8. Full: fallback when the delta cannot be used. Skipping a version means the delta no longer matches, so the full package is downloaded automatically. Updating from a version that predates the move of the program files also always takes the full package, because a partial download would leave the required files incomplete (ResolveDownloadType).
  9. Download progress: _downloadClient (no API headers, 5-minute timeout) streams the download, visualized in the GlowBar (10%–74%). After receipt, ZipFile.ExtractToDirectory extracts into the temp directory (TempUpdateDir). Upon extract completion: IsReadyToRestart = true and RaiseStateChanged().

If the app is closed before the user clicks Restart, the update will NOT be applied silently.

  • UpdateService.UserConsentedToApply is set to true only by ApplyAndRestart() (i.e., the popup's "Restart" button).
  • On exit, App_Exit → UpdateService.ApplyOnExit() is guarded by if (!IsReadyToRestart || !UserConsentedToApply) return;.
  • If the user closes without consenting, the 30-second timer on next launch will re-check and the same popup reappears (the extracted files remain in TempUpdateDir).

Apply batch and restart notification

  • LaunchUpdateBatch() generates a .bat that uses xcopy /s /y /q to overwrite appDir with the contents of extracted/, then restarts with the new binary.
  • The restarted process runs with the --updated command-line argument, which the app detects to show the "Update completed (vX.Y.Z)" toast and to call CleanupTempFiles() to remove the old cache.

Skip feature

Selecting "Skip this version" on the version-available dialog adds the target version to WindowSettings.SkippedVersions, and it will not be notified in future automatic checks (but still appears in manual checks).

Troubleshooting

Symptom Cause and fix
Popup doesn't appear Check the logs (data/logs/*.log) for [Update] StateChanged raised and [Update] ShowRestartPopup called. If they are missing, suspect that _updateServiceSubscribed was never initialized
"rate limit" log appears GitHub API (direct) hit its rate cap. The next check is automatically delayed by 6 hours. Downloads during rate limit fall back to opening the Releases page in the browser
Update doesn't apply after restart The apply batch may have been blocked by antivirus. Check data/logs/ for [Update] ApplyAndRestart and [Update] ApplyOnExit values of IsReadyToRestart / UserConsentedToApply
Want to disable auto-update entirely Control Deck → About → "Enable auto-update" → OFF. Saves WindowSettings.AutoUpdateEnabled = false and stops the timer via SetEnabled(false)

Log spec

The main log prefix for update operations is [Update]. The following log points were added in v0.45.1:

  • [Update] Periodic check started / result: hasUpdate=...
  • [Update] {typeLabel} (v... → v...) — download starts
  • [Update] Download & extract complete IsReadyToRestart=true
  • [Update] StateChanged raised: IsDownloading=..., IsReadyToRestart=..., AvailableVersion=...
  • [Update] ShowRestartPopup called from {source}, version=...
  • [Update] ApplyAndRestart: user consented, launching batch
  • [Update] ApplyOnExit: IsReadyToRestart=..., UserConsentedToApply=...