Files

98 lines
2.6 KiB
Go

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
}