98 lines
2.6 KiB
Go
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
|
|
}
|