Show collection bars + Top Tracks for source artists, upgrade source clicks to library when possible

Three issues from screenshots:

1. .collection-overview was hidden for source artists (CSS rule too
   aggressive). It actually renders fine — just shows 0/N "missing"
   for each release type, which is useful info. Removed from the hide
   rules.

2. #artist-hero-sidebar (Top Tracks "Popular on Last.fm") was also
   hidden. The renderer (_loadArtistTopTracks in library.js) already
   fetches by artist name via /api/artist/0/lastfm-top-tracks, so it
   works for source artists too. Removed from the hide rules.

3. Clicking a source-artist result for someone you ALREADY have in
   the library was loading the bare source view instead of the
   library view (bug). Backend now does a "library upgrade" lookup
   in get_artist_detail: when the direct ID lookup misses but a
   source param is provided, search the artists table by the source-
   specific ID column (deezer_id / spotify_artist_id / etc.). If a
   match exists, use that library PK and the rest of the library
   path runs normally — owned releases, enrichment, completion
   bars, all the goodies you'd see if you'd clicked from Library.
   Falls back to a name match within the active server, then to the
   source-only response if nothing matches.

The remaining library-only items (artist-enrichment-coverage, Radio
button, Enhance Quality button) stay hidden for source artists since
they all require owned tracks.
This commit is contained in:
Broque Thomas 2026-04-22 19:19:48 -07:00
parent 5c94b87aea
commit 9106617538
2 changed files with 70 additions and 10 deletions

View file

@ -11331,6 +11331,51 @@ _SOURCE_ID_FIELD = {
}
def _find_library_artist_for_source(database, source, source_artist_id, artist_name):
"""Try to upgrade a source-artist click to a library lookup.
Returns the library PK of an artist that matches either the source-specific
ID column (e.g. WHERE deezer_id = source_artist_id) or, as a fallback, the
artist name in the active server. Returns None if no match.
"""
column = _SOURCE_ID_FIELD.get(source)
if not column:
return None
try:
with database._get_connection() as conn:
cursor = conn.cursor()
# Match by source-specific ID column. Server-source-agnostic: any
# library record (Plex/Jellyfin/Navidrome/SoulSync) that has the
# right external ID is a hit.
cursor.execute(
f"SELECT id, name FROM artists WHERE {column} = ? LIMIT 1",
(str(source_artist_id),)
)
row = cursor.fetchone()
if row:
return row[0]
# Fallback: case-insensitive name match within the active server only,
# to avoid jumping the user across server contexts unintentionally.
if artist_name:
try:
active_server = config_manager.get_active_media_server()
except Exception:
active_server = None
if active_server:
cursor.execute(
"SELECT id FROM artists "
"WHERE LOWER(name) = LOWER(?) AND server_source = ? LIMIT 1",
(artist_name, active_server)
)
row = cursor.fetchone()
if row:
return row[0]
except Exception as e:
logger.debug(f"Library upgrade lookup failed for {source}:{source_artist_id}: {e}")
return None
def _build_source_only_artist_detail(artist_id, artist_name, source):
"""Synthesize an artist-detail response from a single metadata source for an
artist that isn't in the local library. Used when `/api/artist-detail/<id>`
@ -11514,9 +11559,26 @@ def get_artist_detail(artist_id):
# Get artist discography from database
db_result = database.get_artist_discography(artist_id)
# Library upgrade: if direct ID lookup missed AND we have a source hint,
# check whether the user already owns this artist in the library under
# a different ID (e.g. clicking a Deezer search result for an artist
# they have indexed in Plex). Prefer the library record so they get
# all their owned releases + enrichment instead of a bare source view.
if not db_result.get('success') and source_param in _SOURCE_ONLY_ARTIST_SOURCES:
library_pk = _find_library_artist_for_source(
database, source_param, artist_id, artist_name_arg
)
if library_pk:
logger.info(
f"Source-id {source_param}:{artist_id} matched library artist "
f"PK={library_pk} — upgrading to library response"
)
db_result = database.get_artist_discography(library_pk)
if not db_result.get('success'):
# Library lookup failed. If a metadata source was specified, fall back
# to a source-only response so the page can render a non-library artist.
# Library lookup still failed. If a metadata source was specified,
# fall back to a source-only response so the page can render a
# non-library artist.
if source_param in _SOURCE_ONLY_ARTIST_SOURCES:
return _build_source_only_artist_detail(
artist_id, artist_name_arg, source_param

View file

@ -59400,20 +59400,18 @@ body[data-artist-source="source"] #artist-detail-page #enhanced-view-container {
display: none !important;
}
/* Library-only: completion bars, enrichment coverage, Top Tracks sidebar, Radio / Enhance Quality buttons */
body[data-artist-source="source"] #artist-detail-page .collection-overview,
/* Library-only buttons: Radio + Enhance Quality both require owned tracks.
Enrichment coverage is a per-track DB enrichment percentage that doesn't
apply to artists you don't own.
.collection-overview and #artist-hero-sidebar both render usefully for
source artists (collection shows 0/N missing, Top Tracks pulls from
Last.fm by name) so they stay visible. */
body[data-artist-source="source"] #artist-detail-page .artist-enrichment-coverage,
body[data-artist-source="source"] #artist-detail-page #artist-hero-sidebar,
body[data-artist-source="source"] #artist-detail-page #library-artist-radio-btn,
body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-btn {
display: none !important;
}
/* Library-only: section-stats "0 owned / 0 missing" counts don't apply */
body[data-artist-source="source"] #artist-detail-page .section-stats {
display: none !important;
}
/* =========================================================================
Standalone /artist-detail page hero blurred-background treatment