Keep posters and casts between runs
Posters (the downloaded bytes, per size, 30 days, at most 200 MB) and casts (people.json, 7 days, at most 2000) are stored in the user's cache directory (os.UserCacheDir()/arr-tui) and used on the next start before asking the servers. Expired posters are pruned at startup, casts are written shortly after new ones arrive and on exit. Tests turn the cache off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+62
-8
@@ -3,26 +3,83 @@ package ui
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/trakt"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPeopleCache = 500
|
||||
peopleRetryWait = 2 * time.Minute // a failed lookup is not repeated sooner
|
||||
peopleSaveDelay = 5 * time.Second // new casts are written to disk this long after the last one
|
||||
)
|
||||
|
||||
// peopleCache holds the casts loaded from Trakt and the lookups in flight.
|
||||
// UI goroutine only.
|
||||
// peopleCache holds the casts loaded from Trakt (also those kept on disk from
|
||||
// earlier runs, see diskcache.go) and the lookups in flight. UI goroutine only.
|
||||
type peopleCache struct {
|
||||
done map[string]trakt.People
|
||||
saved map[string]time.Time // when each cast was fetched
|
||||
failed map[string]time.Time
|
||||
waiting map[string][]func(trakt.People, error)
|
||||
dirty bool // casts not written to disk yet
|
||||
timer *time.Timer // writes them
|
||||
}
|
||||
|
||||
func newPeopleCache() *peopleCache {
|
||||
return &peopleCache{done: map[string]trakt.People{}, failed: map[string]time.Time{}, waiting: map[string][]func(trakt.People, error){}}
|
||||
return &peopleCache{done: map[string]trakt.People{}, saved: map[string]time.Time{}, failed: map[string]time.Time{},
|
||||
waiting: map[string][]func(trakt.People, error){}}
|
||||
}
|
||||
|
||||
// loadSavedPeople fills the cache with the casts stored on disk.
|
||||
func (a *App) loadSavedPeople() {
|
||||
for k, p := range a.disk.loadPeople() {
|
||||
a.people.done[k] = trakt.People{Cast: p.Cast, Leaders: p.Leaders}
|
||||
a.people.saved[k] = p.Saved
|
||||
}
|
||||
}
|
||||
|
||||
// remember adds a fetched cast, dropping the oldest ones beyond
|
||||
// maxPeopleSaved, and has it written to disk soon.
|
||||
func (a *App) remember(key string, p trakt.People) {
|
||||
c := a.people
|
||||
c.done[key], c.saved[key] = p, time.Now()
|
||||
if len(c.done) > maxPeopleSaved {
|
||||
keys := make([]string, 0, len(c.saved))
|
||||
for k := range c.saved {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.SortFunc(keys, func(x, y string) int { return c.saved[x].Compare(c.saved[y]) })
|
||||
for _, k := range keys[:len(keys)-maxPeopleSaved] {
|
||||
delete(c.done, k)
|
||||
delete(c.saved, k)
|
||||
}
|
||||
}
|
||||
c.dirty = true
|
||||
if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
}
|
||||
c.timer = time.AfterFunc(peopleSaveDelay, func() { a.post(a.savePeople) })
|
||||
}
|
||||
|
||||
// savePeople writes the casts to disk if there are new ones (UI goroutine;
|
||||
// the writing itself happens in the background).
|
||||
func (a *App) savePeople() {
|
||||
c := a.people
|
||||
if !c.dirty {
|
||||
return
|
||||
}
|
||||
c.dirty = false
|
||||
all := a.peopleSnapshot()
|
||||
go func() { _ = a.disk.savePeople(all) }()
|
||||
}
|
||||
|
||||
func (a *App) peopleSnapshot() map[string]savedPeople {
|
||||
c := a.people
|
||||
all := make(map[string]savedPeople, len(c.done))
|
||||
for k, p := range c.done {
|
||||
all[k] = savedPeople{Saved: c.saved[k], Cast: p.Cast, Leaders: p.Leaders}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
// loadPeople delivers the cast of a title to done on the UI goroutine,
|
||||
@@ -59,10 +116,7 @@ func (a *App) loadPeople(r trakt.Ref, done func(trakt.People, error)) {
|
||||
if err != nil {
|
||||
c.failed[key] = time.Now()
|
||||
} else {
|
||||
if len(c.done) >= maxPeopleCache {
|
||||
c.done = map[string]trakt.People{}
|
||||
}
|
||||
c.done[key] = p
|
||||
a.remember(key, p)
|
||||
}
|
||||
for _, w := range waiters {
|
||||
w(p, err)
|
||||
|
||||
Reference in New Issue
Block a user