A proper artist/album/track blacklist (distinct from download_blacklist, which stays untouched). ID-keyed across metadata sources so a ban survives a source switch; profile-scoped; cascade artist→album→track. - core/blocklist/matching.py — pure decision core (no I/O): build an index from rows, candidate_block_reason() walks track→album→artist. Same-source ID match is primary; artist NAME is a fallback (covers the ID-backfill window); albums/tracks are ID-only (common titles like "Greatest Hits" must not false-positive across artists). Source-isolated so a numeric Deezer id can't collide with a numeric iTunes id of a different entity. - DB: new `blocklist` table (profile_id, entity_type, name, 4 source-id cols, match_status) + CRUD, match-row fetch, backfill-pending query, id-backfill update (COALESCE — fills NULLs only). - Guard: _wishlist_blocklist_reason at the top of add_to_wishlist — every auto-acquisition path funnels through it, so one check covers watchlist, discography backfill, repair, manual add. Fails OPEN (a guard error never blocks a legitimate add). - Discovery unified IN: legacy discovery_artist_blacklist is migrated into the blocklist on upgrade (replicated to every profile so no global ban silently stops working; idempotent; legacy table kept for rollback). Discovery reads (hero + personalized-playlist SQL) now union the blocklist, so a new-modal ban filters discovery too. Tests: 13 on the pure matcher (cascade, id-vs-name rules, source isolation, precedence) + 10 on the DB/guard (CRUD, profile isolation, dedup, backfill, end-to-end wishlist refusal + cascade + the discovery migration upgrade path). 50 blocklist/personalized tests pass.
0 lines
Python
0 lines
Python