refactor(sections): tidy trending_discover fetch and cache helpers
Extract newTrendingEntry, reuse orderMediaItems, and collapse concurrent cache-miss loads with singleflight. Baseline for the persistent snapshot work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2353,6 +2353,19 @@ type trendingDiscoverEntry struct {
|
||||
mediaType string // "movie" | "tv"
|
||||
}
|
||||
|
||||
// newTrendingEntry normalizes a provider entry into a trendingDiscoverEntry,
|
||||
// stringifying the numeric external IDs and dropping any that are unset (<= 0).
|
||||
func newTrendingEntry(tmdbID, tvdbID int, imdbID, mediaType string) trendingDiscoverEntry {
|
||||
e := trendingDiscoverEntry{imdbID: imdbID, mediaType: mediaType}
|
||||
if tmdbID > 0 {
|
||||
e.tmdbID = strconv.Itoa(tmdbID)
|
||||
}
|
||||
if tvdbID > 0 {
|
||||
e.tvdbID = strconv.Itoa(tvdbID)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// fetchTrendingDiscover surfaces external global trending (TMDB or Trakt),
|
||||
// mixing movies + series, matched to titles in the viewer's enabled libraries.
|
||||
// The external fetch + ID resolution are cached briefly so it does not hit the
|
||||
@@ -2400,20 +2413,9 @@ func (f *Fetcher) fetchTrendingDiscover(ctx context.Context, s ResolvedSection,
|
||||
|
||||
// Re-order to trending rank (fetchItemsByContentIDs returns DB order) and
|
||||
// truncate to the section's display limit.
|
||||
byID := make(map[string]*models.MediaItem, len(items))
|
||||
for _, item := range items {
|
||||
byID[item.ContentID] = item
|
||||
}
|
||||
ordered := make([]*models.MediaItem, 0, len(orderedIDs))
|
||||
for _, id := range orderedIDs {
|
||||
item, ok := byID[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ordered = append(ordered, item)
|
||||
if len(ordered) >= limit {
|
||||
break
|
||||
}
|
||||
ordered := orderMediaItems(items, orderedIDs)
|
||||
if len(ordered) > limit {
|
||||
ordered = ordered[:limit]
|
||||
}
|
||||
return ordered, len(ordered), nil
|
||||
}
|
||||
@@ -2423,27 +2425,42 @@ func (f *Fetcher) fetchTrendingDiscover(ctx context.Context, s ResolvedSection,
|
||||
func (f *Fetcher) loadTrendingDiscoverContentIDs(ctx context.Context, source, window string, fetchLimit int) ([]string, error) {
|
||||
cache := f.ensureEditorialCandidateCache()
|
||||
cacheKey := fmt.Sprintf("trending_discover|%s|%s|%d", source, window, fetchLimit)
|
||||
now := f.Clock.Now()
|
||||
if cached, ok := cache.get(cacheKey, now); ok {
|
||||
if cached, ok := cache.get(cacheKey, f.now()); ok {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
entries, err := f.fetchTrendingDiscoverEntries(ctx, source, window, fetchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Don't cache when the provider is unconfigured/empty, so a newly-configured
|
||||
// provider takes effect immediately rather than after the TTL.
|
||||
if len(entries) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
// Collapse concurrent cache-miss loads (e.g. many home-page requests at TTL
|
||||
// expiry) into a single upstream fetch + ID resolution, as
|
||||
// cachedEditorialCandidates does for editorial sections.
|
||||
value, err, _ := f.candidateGroup.Do(cacheKey, func() (any, error) {
|
||||
now := f.now()
|
||||
if cached, ok := cache.get(cacheKey, now); ok {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
contentIDs, err := f.resolveTrendingDiscoverIDs(ctx, entries)
|
||||
entries, err := f.fetchTrendingDiscoverEntries(ctx, source, window, fetchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Don't cache when the provider is unconfigured/empty, so a
|
||||
// newly-configured provider takes effect immediately rather than after
|
||||
// the TTL.
|
||||
if len(entries) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
contentIDs, err := f.resolveTrendingDiscoverIDs(ctx, entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache.set(cacheKey, contentIDs, now.Add(time.Hour))
|
||||
return contentIDs, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache.set(cacheKey, contentIDs, now.Add(time.Hour))
|
||||
return contentIDs, nil
|
||||
contentIDs, _ := value.([]string)
|
||||
return append([]string(nil), contentIDs...), nil
|
||||
}
|
||||
|
||||
// fetchTrendingDiscoverEntries pulls the raw trending list from the configured
|
||||
@@ -2460,17 +2477,12 @@ func (f *Fetcher) fetchTrendingDiscoverEntries(ctx context.Context, source, wind
|
||||
if movieErr != nil && showErr != nil {
|
||||
return nil, fmt.Errorf("trakt trending: %v / %v", movieErr, showErr)
|
||||
}
|
||||
merged := append(append([]catalog.TraktCollectionEntry{}, movies...), shows...)
|
||||
out := make([]trendingDiscoverEntry, 0, len(merged))
|
||||
for _, e := range merged {
|
||||
entry := trendingDiscoverEntry{imdbID: e.IMDbID, mediaType: e.MediaType}
|
||||
if e.TMDBID > 0 {
|
||||
entry.tmdbID = strconv.Itoa(e.TMDBID)
|
||||
}
|
||||
if e.TVDBID > 0 {
|
||||
entry.tvdbID = strconv.Itoa(e.TVDBID)
|
||||
}
|
||||
out = append(out, entry)
|
||||
out := make([]trendingDiscoverEntry, 0, len(movies)+len(shows))
|
||||
for _, e := range movies {
|
||||
out = append(out, newTrendingEntry(e.TMDBID, e.TVDBID, e.IMDbID, e.MediaType))
|
||||
}
|
||||
for _, e := range shows {
|
||||
out = append(out, newTrendingEntry(e.TMDBID, e.TVDBID, e.IMDbID, e.MediaType))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -2485,14 +2497,7 @@ func (f *Fetcher) fetchTrendingDiscoverEntries(ctx context.Context, source, wind
|
||||
}
|
||||
out := make([]trendingDiscoverEntry, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
entry := trendingDiscoverEntry{imdbID: e.IMDbID, mediaType: e.MediaType}
|
||||
if e.ID > 0 {
|
||||
entry.tmdbID = strconv.Itoa(e.ID)
|
||||
}
|
||||
if e.TVDBID > 0 {
|
||||
entry.tvdbID = strconv.Itoa(e.TVDBID)
|
||||
}
|
||||
out = append(out, entry)
|
||||
out = append(out, newTrendingEntry(e.ID, e.TVDBID, e.IMDbID, e.MediaType))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user