feat(cli): upgrade command, rich status, and version cache
- 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
This commit is contained in:
parent
01d62ffa13
commit
3e0f3a5a64
33 changed files with 7084 additions and 65 deletions
75
internal/upgrade/cache.go
Normal file
75
internal/upgrade/cache.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue