From 410bddd10249438b3de9b5fbd7e53081c9aeeacd Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:33:02 -0700 Subject: [PATCH] Redesign download history with collapsible entries and full provenance Entries are now compact cards that expand on click to reveal source details. Shows expected vs downloaded title/artist with red mismatch highlighting. Source artist column added to DB. Streaming track IDs extracted from the id||name filename pattern. File and ID always on their own line to avoid edge-case misplacement. --- database/music_database.py | 12 +++--- web_server.py | 18 +++++++-- webui/static/script.js | 58 ++++++++++++++++++++------- webui/static/style.css | 82 ++++++++++++++++++++++++++++++++++---- 4 files changed, 140 insertions(+), 30 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index 930ea223..5abcd165 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -509,7 +509,7 @@ class MusicDatabase: if 'download_source' not in lh_cols: cursor.execute("ALTER TABLE library_history ADD COLUMN download_source TEXT") logger.info("Added download_source column to library_history") - for _col in ['source_track_id', 'source_track_title', 'source_filename', 'acoustid_result']: + for _col in ['source_track_id', 'source_track_title', 'source_filename', 'acoustid_result', 'source_artist']: if _col not in lh_cols: cursor.execute(f"ALTER TABLE library_history ADD COLUMN {_col} TEXT") logger.info(f"Added {_col} column to library_history") @@ -9622,7 +9622,7 @@ class MusicDatabase: def add_library_history_entry(self, event_type, title, artist_name=None, album_name=None, quality=None, server_source=None, file_path=None, thumb_url=None, download_source=None, source_track_id=None, source_track_title=None, - source_filename=None, acoustid_result=None): + source_filename=None, acoustid_result=None, source_artist=None): """Record a download or import event to the library history table.""" try: conn = self._get_connection() @@ -9630,10 +9630,12 @@ class MusicDatabase: cursor.execute(""" INSERT INTO library_history (event_type, title, artist_name, album_name, quality, server_source, file_path, thumb_url, download_source, - source_track_id, source_track_title, source_filename, acoustid_result) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + source_track_id, source_track_title, source_filename, + acoustid_result, source_artist) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, (event_type, title, artist_name, album_name, quality, server_source, file_path, thumb_url, - download_source, source_track_id, source_track_title, source_filename, acoustid_result)) + download_source, source_track_id, source_track_title, source_filename, + acoustid_result, source_artist)) conn.commit() return True except Exception as e: diff --git a/web_server.py b/web_server.py index f780e6d5..6b6f0efb 100644 --- a/web_server.py +++ b/web_server.py @@ -1916,9 +1916,18 @@ def _record_library_history_download(context): source_track_id = (search_result.get('track_id', '') or search_result.get('id', '') or ti.get('id', '')) - # Source track title: only save if it differs from expected title (otherwise it's just noise) - _src_title = search_result.get('title', '') or search_result.get('name', '') - source_track_title = _src_title if _src_title and _src_title != title else '' + + # Source title/artist — what the download source said the track was. + # For Soulseek: parsed from the peer's filename by TrackResult._parse_filename_metadata() + # For Tidal/YouTube/Qobuz: from the streaming API's own metadata + # These live on the candidate's original fields, NOT the spotify_clean_* fields + source_track_title = search_result.get('title', '') or search_result.get('name', '') + source_artist = search_result.get('artist', '') + # For streaming sources, track ID is encoded in filename as "id||display_name" + if source_filename and '||' in source_filename and username in ('tidal', 'youtube', 'qobuz', 'hifi', 'deezer_dl'): + _stream_id = source_filename.split('||')[0] + if _stream_id and not source_track_id: + source_track_id = _stream_id # AcoustID verification result acoustid_result = context.get('_acoustid_result', '') @@ -1936,7 +1945,8 @@ def _record_library_history_download(context): source_track_id=source_track_id, source_track_title=source_track_title, source_filename=source_filename, - acoustid_result=acoustid_result + acoustid_result=acoustid_result, + source_artist=source_artist ) except Exception: pass # Non-critical, never block download flow diff --git a/webui/static/script.js b/webui/static/script.js index 784d03c1..fdd17bc4 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -21093,25 +21093,55 @@ function renderHistoryEntry(entry) { const meta = [entry.artist_name, entry.album_name].filter(Boolean).join(' — '); - // Source provenance detail line + // Source provenance — expected vs downloaded let sourceDetail = ''; - if (entry.event_type === 'download' && (entry.source_filename || entry.source_track_title)) { - const parts = []; - if (entry.source_filename) parts.push(`File: ${escapeHtml(entry.source_filename)}`); - else if (entry.source_track_title) parts.push(`Source track: ${escapeHtml(entry.source_track_title)}`); - if (entry.source_track_id) parts.push(`ID: ${escapeHtml(entry.source_track_id)}`); - sourceDetail = `