Files
MeBox/internal/service/discover_section_cache.go
T
2026-06-30 20:18:02 +08:00

105 lines
2.6 KiB
Go

package service
import (
"fmt"
"sync"
"time"
)
// DiscoverSectionCache keeps the last good discover rail in memory so a slow
// upstream provider does not turn a populated page into empty rows.
type DiscoverSectionCache struct {
ttl time.Duration
mu sync.RWMutex
entries map[string]discoverSectionCacheEntry
}
type discoverSectionCacheEntry struct {
items []ExternalMediaResult
storedAt time.Time
}
func NewDiscoverSectionCache(ttl time.Duration) *DiscoverSectionCache {
if ttl <= 0 {
ttl = 6 * time.Hour
}
return &DiscoverSectionCache{
ttl: ttl,
entries: map[string]discoverSectionCacheEntry{},
}
}
func (d *DiscoverService) RememberSection(key string, page int, items []ExternalMediaResult) {
if d == nil || d.sectionCache == nil || len(items) == 0 {
return
}
d.sectionCache.Set(key, page, items)
}
func (d *DiscoverService) CachedSection(key string, page int) ([]ExternalMediaResult, bool) {
if d == nil || d.sectionCache == nil {
return nil, false
}
return d.sectionCache.Get(key, page)
}
func (c *DiscoverSectionCache) Set(key string, page int, items []ExternalMediaResult) {
if c == nil || key == "" || page < 1 || len(items) == 0 {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.entries[discoverSectionCacheKey(key, page)] = discoverSectionCacheEntry{
items: cloneExternalMediaResults(items),
storedAt: time.Now(),
}
}
func (c *DiscoverSectionCache) Get(key string, page int) ([]ExternalMediaResult, bool) {
if c == nil || key == "" || page < 1 {
return nil, false
}
c.mu.RLock()
entry, ok := c.entries[discoverSectionCacheKey(key, page)]
c.mu.RUnlock()
if !ok || time.Since(entry.storedAt) > c.ttl || len(entry.items) == 0 {
return nil, false
}
return cloneExternalMediaResults(entry.items), true
}
func discoverSectionCacheKey(key string, page int) string {
return fmt.Sprintf("%s:%d", key, page)
}
func cloneExternalMediaResults(items []ExternalMediaResult) []ExternalMediaResult {
out := make([]ExternalMediaResult, len(items))
for i, item := range items {
out[i] = item
out[i].SubscribeAliases = cloneStrings(item.SubscribeAliases)
out[i].MissingEpisodes = cloneInts(item.MissingEpisodes)
out[i].Languages = cloneStrings(item.Languages)
out[i].Countries = cloneStrings(item.Countries)
out[i].Genres = cloneStrings(item.Genres)
}
return out
}
func cloneStrings(items []string) []string {
if len(items) == 0 {
return nil
}
out := make([]string, len(items))
copy(out, items)
return out
}
func cloneInts(items []int) []int {
if len(items) == 0 {
return nil
}
out := make([]int, len(items))
copy(out, items)
return out
}