Commit Graph
8 Commits
Author SHA1 Message Date
yonlu bb015092d4 fix: prevent search index build from starving library scan for DB access
The search index build and library scan both write to the same
single-connection SQLite DB. The index build runs continuous batch
transactions that can starve the scan's clearLibraryTables call,
causing the scan to silently hang without logging.

Fix: decouple index build from SetContext. The build now starts
AFTER the soft scan completes on startup. For full rescans, the
PreClear hook stops the index build, and PostScan restarts it.

Also made StartBuild/StopBuild safe for multiple calls:
- StartBuild is a no-op if already running
- StopBuild is a no-op if not running (no deadlock on done channel)
- done channel created per-build, not in constructor
2026-03-26 15:21:01 -04:00
yonlu 8f6a4c6a8e feat: 'In Library' badges, artist images on local pages, MBID-based Tier 3
Three features wired together:

1. 'In Library' badges on explore search results:
   CheckLibraryMBIDs Wails binding batch-checks which search result
   MBIDs exist in the local library. Green badges render on matching
   artist cards and album cards.

2. Artist images on local artist-details page:
   Local artist pages now call GetArtistMBID(name) to resolve the
   MBID from tags, then GetArtistImageURL(mbid) to fetch the cached
   Wikimedia photo. Falls back to initial-letter avatar.

3. Tier 3 search index uses direct MBIDs from tags:
   buildTier3Library now reads artists.mbid column (from audio tags)
   for direct MBID matching, falling back to name matching for
   untagged artists. Eliminates false matches and catches artists
   that name matching misses.
2026-03-26 09:34:57 -04:00
yonlu c9b3c86f37 perf: unified per-artist indexing — discography + image in parallel
Restructure indexOneArtist to run LB discography fetches and MB
artist image resolution concurrently. They use different rate
limiters (LB: 3 req/s, MB: 1 req/s) so they overlap without
contention.

Per artist, the indexer now runs two parallel pipelines:
  LB pipeline: top-release-groups + top-recordings
  MB pipeline: url-rels → Wikidata P18 → Wikimedia image fetch

All artist images are pre-cached during the index build instead
of being resolved on-demand during search. Total build time
drops from ~105 min (sequential) to ~63 min (parallel, MB-bound).

SearchIndex now takes ArtistImageProvider as a dependency. The
Service constructor creates artistImg before the index so both
can share it.
2026-03-26 08:54:43 -04:00
yonlu 49a26c6163 feat: cover art proxy with disk cache for instant thumbnail loading
Add CoverArtProxy that fetches cover art from CAA, caches the image
bytes on disk (~/.local/share/yellowjacket/cover-art-cache/), and
returns base64 data URLs via the GetThumbnail Wails binding.

First load: fetches from CAA (rate-limited), caches to disk.
Subsequent loads: instant from disk cache, no network.
404s: cached as empty files to avoid re-fetching.

Frontend explore-view loads thumbnails async via GetThumbnail()
calls that fire during render. Cached thumbnails appear as data
URLs directly in img src, bypassing the browser's HTTP stack.
Uncached thumbnails fall back to the CAA URL while the proxy
fetches in the background, then re-render with the cached version.

Also stores caa_id and caa_release_mbid in the search index's
extra_json for future direct Internet Archive URL construction.
2026-03-25 14:09:30 -04:00
yonlu e4f4639ab7 feat: per-tier refresh intervals with incremental discography builds
Replace the single 7-day full rebuild with per-tier scheduling:

- Tier 1 (sitewide top lists): weekly refresh, 12 API calls
- Tiers 2-4 (discographies): monthly refresh, incremental —
  only fetches discographies for artists not already indexed

On subsequent runs:
- If T1 is fresh, load cached artists from the index (~0 calls)
- If discographies are fresh, skip Tiers 2-4 entirely (~0 calls)
- If discographies are stale, diff against indexed set and only
  fetch new artists that appeared in the sitewide lists

Add helpers: isMetaFresh (per-key freshness check),
loadCachedSitewideArtists (read artists from existing index),
filterUnindexed (diff artist list against indexed set).

After first build: typical startup is <5s (T1 cache load).
Monthly incremental: ~50-100 calls for newly appeared artists.
2026-03-25 13:43:00 -04:00
yonlu 59d5b7272e feat: popularity-scaled per-artist index budgets
Instead of fixed 20 RGs + 100 recordings for every artist, scale
the budget by popularity using a power curve (exponent 0.3):

  Radiohead (2.5M listens): 20 RGs, 100 recordings
  Hans Zimmer (715K):       15 RGs, 71 recordings
  Clutch (178K):            11 RGs, 50 recordings
  Similar (~10K):            7 RGs, 27 recordings
  Organic (unknown):         5 RGs, 10 recordings

Saves ~53% index size (~29 MB vs ~62 MB) with identical API calls.
The savings come from T4 similar artists (long tail) where full
discographies were wasteful. Top artists still get full coverage.
2026-03-25 13:11:15 -04:00
yonlu 48acede1bc feat: 5-tier search index with library + similar artist expansion
Rewrite SearchIndex with tiered background build:

Tier 1 — Sitewide instant (<5s, 12 calls): top artists, recordings,
  and release groups across 4 time ranges. Searchable immediately.

Tier 2 — Sitewide full discog (~16min, 2881 calls): top 20 RGs +
  top 100 recordings per sitewide artist (~1440 unique artists from
  all_time/this_year/this_month/this_week union).

Tier 3 — Library artists (~4min, 664 calls): match local library
  artist names against known MBIDs, index their full discographies.
  Catches the user's personal taste that sitewide misses.

Tier 4 — Similar artists (~24min, ~4300 calls): fetch similar
  artists from LB labs for each library artist, index their
  discographies. Fans out into the user's taste neighborhood.

Tier 5 — Organic growth (0 calls): BrowseReleaseGroups now writes
  to the search index in a background goroutine. Every artist page
  view adds that artist's discography to the index for free.

Other changes:
- indexRGsPerArtist bumped 10→20 (96% vs 88% coverage)
- indexRecsPerArtist bumped 10→100 (track-name searchability)
- indexMinPopularity = 50 (cuts noise from long tails)
- Dedicated 3 req/s rate limiter for indexer
- Labs similar-artists endpoint at labs.api.listenbrainz.org
- Each tier marks index as ready on completion so search improves
  progressively during the ~44min total build
2026-03-25 12:49:20 -04:00
yonlu bba4e1f3de feat: SearchIndex — background build + FTS5 query for popularity index
New SearchIndex struct in searchindex.go:
- Background build fetches top 1000 LB artists, then their top 10
  release groups and top 10 recordings (2001 API calls total)
- Dedicated 3 req/s rate limiter for indexer (LB allows 30/10s)
- Bounded concurrency (3 goroutines) with progress logging
- Batch INSERTs in transactions of 100 rows
- FTS5 query with prefix matching ('for you' → 'for* you*')
- Results sorted by popularity descending
- Skips rebuild if index is < 7 days old
- Marks index ready from existing rows if build fails
- Context cancellation for clean shutdown
2026-03-25 09:39:22 -04:00