auto-update: drop confirmation dialog, do it silently
Chrome-style silent updater: no prompt, no progress UI, no buttons. On startup we check for updates (8s timeout), and if one is found we download + verify + apply + relaunch — total ~3-5s on a fast connection. The user sees the new version's window instead of the old one, period. Errors that do warrant a dialog (apply failed mid-write, sha256 mismatch, can't relaunch) still surface as a message box so the user knows their copy is on the previous version. 'No update' and network timeouts stay silent. Split timeouts so a 60s download doesn't get killed by the 8s check budget. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,32 +14,35 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// autoUpdateOnStartup runs a non-interactive update check whenever drover.exe
|
||||
// starts as a GUI app (no CLI subcommand). If an update is available, a
|
||||
// Yes/No message box prompts the user; on Yes we download, verify, apply
|
||||
// the update, then re-launch the binary so the new version is what they see.
|
||||
// autoUpdateOnStartup silently checks for and applies updates whenever
|
||||
// drover.exe starts as a GUI app (no CLI subcommand). Chrome-style: no
|
||||
// prompt, no progress bar, no questions — if an update is available we
|
||||
// download, verify, apply, and re-launch. The user just sees the new
|
||||
// version's window appear instead of the old one.
|
||||
//
|
||||
// Network failures, server outages, and "no updates available" are silent
|
||||
// fall-throughs — startup must never block on them.
|
||||
// Network failures, server outages, slow downloads, and "no updates
|
||||
// available" are silent fall-throughs — startup must never block on them.
|
||||
//
|
||||
// Two split contexts:
|
||||
// - check (8s) — quick HEAD-equivalent against the releases API
|
||||
// - apply (60s) — actual download + sha256 + atomic replace
|
||||
func autoUpdateOnStartup() {
|
||||
// Tight timeout — startup, not a long-running task.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||
defer cancel()
|
||||
|
||||
src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe")
|
||||
rel, hasUpdate, err := updater.CheckForUpdate(ctx, src, Version)
|
||||
|
||||
checkCtx, cancelCheck := context.WithTimeout(context.Background(), 8*time.Second)
|
||||
rel, hasUpdate, err := updater.CheckForUpdate(checkCtx, src, Version)
|
||||
cancelCheck()
|
||||
if err != nil || !hasUpdate {
|
||||
// Silent: offline, slow network, or already up to date — none of
|
||||
// these should interrupt the user.
|
||||
return
|
||||
}
|
||||
|
||||
if !confirmUpdateDialog(rel) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := updater.ApplyUpdate(ctx, rel, nil); err != nil {
|
||||
errorDialog(fmt.Sprintf("Update failed: %v", err))
|
||||
applyCtx, cancelApply := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancelApply()
|
||||
if err := updater.ApplyUpdate(applyCtx, rel, nil); err != nil {
|
||||
// Apply failed — surface this one (sha mismatch, write error,
|
||||
// disk full are not silent-fail cases). The user can keep using
|
||||
// the current version after dismissing the dialog.
|
||||
errorDialog(fmt.Sprintf("Update to %s failed: %v\n\nContinuing on current version.", rel.TagName, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,36 +51,10 @@ func autoUpdateOnStartup() {
|
||||
return
|
||||
}
|
||||
|
||||
// Successfully spawned the new version — exit cleanly so it can take over.
|
||||
// Successfully spawned the new version — exit cleanly so it takes over.
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// confirmUpdateDialog asks the user whether to apply the available update.
|
||||
// Returns true on Yes (IDYES = 6).
|
||||
func confirmUpdateDialog(rel *updater.Release) bool {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
messageBox := user32.NewProc("MessageBoxW")
|
||||
|
||||
body := fmt.Sprintf(
|
||||
"A new version is available.\n\n"+
|
||||
"Current: v%s\n"+
|
||||
"Latest: %s\n\n"+
|
||||
"Install it now? Drover-Go will restart automatically.",
|
||||
Version, rel.TagName,
|
||||
)
|
||||
title := "Drover-Go — Update available"
|
||||
|
||||
bodyW, _ := windows.UTF16PtrFromString(body)
|
||||
titleW, _ := windows.UTF16PtrFromString(title)
|
||||
|
||||
// MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND | MB_TOPMOST | MB_DEFBUTTON1
|
||||
const flags = 0x00000004 | 0x00000020 | 0x00010000 | 0x00040000 | 0x00000000
|
||||
|
||||
r, _, _ := messageBox.Call(0, uintptr(unsafe.Pointer(bodyW)), uintptr(unsafe.Pointer(titleW)), flags)
|
||||
const IDYES = 6
|
||||
return r == IDYES
|
||||
}
|
||||
|
||||
func errorDialog(msg string) {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
messageBox := user32.NewProc("MessageBoxW")
|
||||
|
||||
Reference in New Issue
Block a user