- Replace `upgrade` stub with real command (alias for `self-update`) - Also register `update` as alias: `unarr update` works too - Rewrite `status` to show full config, disk usage, daemon state, and update availability with colored sections - Add version check cache (1h TTL) so `status` is instant on repeat runs - Guard against division by zero on empty filesystems - Guard against negative durations from clock skew - Guard against stale PID via heartbeat recency check (2 min) - Add comprehensive test coverage across agent, engine, upgrade, usenet, arr, library, mediaserver, and UI packages - Improve Makefile coverage target to exclude cmd/ glue code - Fix stream handler resource cleanup and ffprobe error handling
75 lines
1.8 KiB
Go
75 lines
1.8 KiB
Go
package upgrade
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/torrentclaw/unarr/internal/config"
|
|
)
|
|
|
|
const cacheTTL = 1 * time.Hour
|
|
|
|
// versionCache is the on-disk structure for cached version checks.
|
|
type versionCache struct {
|
|
Version string `json:"version"`
|
|
CheckedAt time.Time `json:"checkedAt"`
|
|
}
|
|
|
|
// cacheFilePath returns the path to the version cache file.
|
|
func cacheFilePath() string {
|
|
return filepath.Join(config.DataDir(), "latest-version.json")
|
|
}
|
|
|
|
// ReadCachedVersion returns the cached latest version if it's fresh (< cacheTTL).
|
|
// Returns empty string if cache is missing, stale, or corrupt.
|
|
func ReadCachedVersion() string {
|
|
data, err := os.ReadFile(cacheFilePath())
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
var c versionCache
|
|
if json.Unmarshal(data, &c) != nil {
|
|
return ""
|
|
}
|
|
if time.Since(c.CheckedAt) > cacheTTL {
|
|
return ""
|
|
}
|
|
return c.Version
|
|
}
|
|
|
|
// writeCachedVersion writes the latest version to the cache file.
|
|
func writeCachedVersion(version string) {
|
|
c := versionCache{
|
|
Version: version,
|
|
CheckedAt: time.Now(),
|
|
}
|
|
data, err := json.Marshal(c)
|
|
if err != nil {
|
|
return
|
|
}
|
|
path := cacheFilePath()
|
|
os.MkdirAll(filepath.Dir(path), 0o755)
|
|
// Best-effort write — ignore errors
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
|
return
|
|
}
|
|
os.Rename(tmp, path)
|
|
}
|
|
|
|
// CheckLatestCached returns the latest version, using cache when fresh.
|
|
// If cache is stale, fetches from GitHub and updates the cache.
|
|
func CheckLatestCached(ctx context.Context) (version string, fromCache bool, err error) {
|
|
if cached := ReadCachedVersion(); cached != "" {
|
|
return cached, true, nil
|
|
}
|
|
v, err := fetchLatestVersion(ctx)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
writeCachedVersion(v)
|
|
return v, false, nil
|
|
}
|