From 41b5cd1f3422366ae1131844aba43c76c812f460 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:39:34 -0700 Subject: [PATCH] Fix allow_duplicate_tracks setting not saving and wishlist dropping cross-album tracks Two bugs: (1) 'wishlist' was missing from the settings save whitelist, so the toggle silently reset to ON on every page reload. (2) The wishlist cleanup function unconditionally removed tracks sharing the same name+artist regardless of album, ignoring the allow_duplicates setting. Now when allow_duplicates is on, the dedup key includes the album name so same song from different albums can coexist. --- database/music_database.py | 21 +++++++++++++++++---- web_server.py | 2 +- webui/static/helper.js | 2 ++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index 3cb72c2e..9776de5f 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -6682,10 +6682,16 @@ class MusicDatabase: return False def remove_wishlist_duplicates(self, profile_id: int = 1) -> int: - """Remove duplicate tracks from wishlist based on track name + artist. + """Remove duplicate tracks from wishlist. + When allow_duplicate_tracks is True, only removes exact duplicates + (same name + artist + album). When False, removes any track with the + same name + artist regardless of album. Keeps the oldest entry (by date_added) for each duplicate set. Returns the number of duplicates removed.""" try: + from config.settings import config_manager + allow_duplicates = config_manager.get('wishlist.allow_duplicate_tracks', True) + with self._get_connection() as conn: cursor = conn.cursor() @@ -6699,7 +6705,7 @@ class MusicDatabase: all_tracks = cursor.fetchall() # Track seen tracks and duplicates to remove - seen_tracks = {} # Key: (track_name, artist_name), Value: track_id to keep + seen_tracks = {} # Value: track row id to keep duplicates_to_remove = [] for track in all_tracks: @@ -6714,7 +6720,13 @@ class MusicDatabase: else: artist_name = 'unknown' - key = (track_name, artist_name) + if allow_duplicates: + # Include album in the key so same song from different albums survives + album = track_data.get('album', {}) + album_name = (album.get('name', '') if isinstance(album, dict) else str(album)).lower() + key = (track_name, artist_name, album_name) + else: + key = (track_name, artist_name) if key in seen_tracks: # Duplicate found - mark for removal @@ -6735,7 +6747,8 @@ class MusicDatabase: removed_count += 1 conn.commit() - logger.info(f"Removed {removed_count} duplicate tracks from wishlist") + if removed_count > 0: + logger.info(f"Removed {removed_count} duplicate tracks from wishlist (allow_duplicates={allow_duplicates})") return removed_count except Exception as e: diff --git a/web_server.py b/web_server.py index 28626057..93ea2c0c 100644 --- a/web_server.py +++ b/web_server.py @@ -5541,7 +5541,7 @@ def handle_settings(): if 'active_media_server' in new_settings: config_manager.set_active_media_server(new_settings['active_media_server']) - for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'deezer_download', 'lidarr_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'listening_stats', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata', 'hydrabase', 'security', 'discogs', 'library', 'discover']: + for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'deezer_download', 'lidarr_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'listening_stats', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata', 'hydrabase', 'security', 'discogs', 'library', 'discover', 'wishlist']: if service in new_settings: for key, value in new_settings[service].items(): config_manager.set(f'{service}.{key}', value) diff --git a/webui/static/helper.js b/webui/static/helper.js index eade9926..b213ed50 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3605,6 +3605,8 @@ const WHATS_NEW = { { title: 'Fix Spotify API Leaking When Deezer/iTunes is Primary', desc: 'Spotify was being called for watchlist album scanning, similar artist discovery, repair jobs, and the Artists page search even when another source was set as primary. All data-fetching now respects the configured primary source. Spotify playlist sync is unaffected' }, { title: 'Fix OAuth Callback Port Hardcoding', desc: 'Custom callback ports (SOULSYNC_SPOTIFY_CALLBACK_PORT / SOULSYNC_TIDAL_CALLBACK_PORT) are now respected in auth instruction pages and log messages instead of always showing 8888. Added startup diagnostics logging for callback port binding' }, { title: 'Fix Wishlist Button on Non-Dashboard Pages', desc: 'Wishlist button click handler moved to global init so it works from any page, not just the dashboard' }, + { title: 'Fix Allow Duplicates Setting Not Saving', desc: 'The "Allow duplicate tracks across albums" toggle was never persisted — it silently reset to ON on every page reload. Now saves correctly' }, + { title: 'Fix Wishlist Dropping Cross-Album Tracks', desc: 'Wishlist cleanup was removing same-titled tracks from different albums even when Allow Duplicates was enabled. Cleanup now respects the setting — same song from different albums can coexist in the wishlist' }, // --- April 14, 2026 --- { date: 'April 14, 2026' },