feat: check for available updates
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hamlog/internal/applog"
|
||||
)
|
||||
|
||||
// updateCheckURL is the GitHub Releases "latest" endpoint for the public OpsLog
|
||||
// build (the exe lives there; source stays on Gitea). Adjust the repo if needed.
|
||||
const updateCheckURL = "https://api.github.com/repos/GregTroar/OpsLog/releases/latest"
|
||||
|
||||
// UpdateInfo is the result of the startup version check.
|
||||
type UpdateInfo struct {
|
||||
Current string `json:"current"` // this build's version (appVersion)
|
||||
Latest string `json:"latest"` // newest published release, "" if unknown
|
||||
Available bool `json:"available"` // Latest > Current
|
||||
URL string `json:"url"` // release page to open
|
||||
}
|
||||
|
||||
// CheckForUpdate asks GitHub for the latest release and compares it to this
|
||||
// build. Best effort — on any failure it reports "no update" so the app never
|
||||
// nags about a check it couldn't complete.
|
||||
func (a *App) CheckForUpdate() UpdateInfo {
|
||||
out := UpdateInfo{Current: appVersion}
|
||||
client := &http.Client{Timeout: 8 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, updateCheckURL, nil)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
applog.Printf("update: check failed: %v", err)
|
||||
return out
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return out // no release yet (404) or rate-limited — treat as up to date
|
||||
}
|
||||
var r struct {
|
||||
TagName string `json:"tag_name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return out
|
||||
}
|
||||
out.Latest = strings.TrimPrefix(strings.TrimSpace(r.TagName), "v")
|
||||
out.URL = r.HTMLURL
|
||||
out.Available = versionLess(appVersion, out.Latest)
|
||||
if out.Available {
|
||||
applog.Printf("update: newer version available — current=%s latest=%s", appVersion, out.Latest)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// versionLess reports whether version a is older than b. Compares dot-separated
|
||||
// numeric parts ("0.9" < "0.10" < "1.0"); non-numeric junk in a part counts as 0.
|
||||
func versionLess(a, b string) bool {
|
||||
pa := strings.Split(a, ".")
|
||||
pb := strings.Split(b, ".")
|
||||
n := len(pa)
|
||||
if len(pb) > n {
|
||||
n = len(pb)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
ai, bi := 0, 0
|
||||
if i < len(pa) {
|
||||
ai = leadingInt(pa[i])
|
||||
}
|
||||
if i < len(pb) {
|
||||
bi = leadingInt(pb[i])
|
||||
}
|
||||
if ai != bi {
|
||||
return ai < bi
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// leadingInt parses the leading digits of s (e.g. "2beta" → 2), 0 if none.
|
||||
func leadingInt(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
end := 0
|
||||
for end < len(s) && s[end] >= '0' && s[end] <= '9' {
|
||||
end++
|
||||
}
|
||||
if end == 0 {
|
||||
return 0
|
||||
}
|
||||
n, _ := strconv.Atoi(s[:end])
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user