From 429306c7f37cdcb4243f68de3d31706c4a803c37 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:24:42 -0700 Subject: [PATCH] Fix enrichment retry loops, cover art finding dupes, and Spotify rate limit during art scan - All 9 enrichment workers: stop auto-retrying 'error' status items (was infinite loop) Only 'not_found' items retry after configured days; errors require manual full refresh - Cover art dedup: check both 'pending' AND 'resolved' findings to prevent recreation - Cover art scanner: top-level Spotify rate limit check skips Spotify entirely when banned, falls back to iTunes/Deezer only, logs once instead of spamming 429s --- core/audiodb_worker.py | 23 +++++++++-------------- core/deezer_worker.py | 23 +++++++++-------------- core/genius_worker.py | 16 ++++++---------- core/itunes_worker.py | 19 ++++++++----------- core/lastfm_worker.py | 23 ++++++++++------------- core/musicbrainz_worker.py | 23 +++++++++-------------- core/qobuz_worker.py | 23 +++++++++-------------- core/repair_jobs/missing_cover_art.py | 15 +++++++++++++-- core/repair_worker.py | 7 ++++--- core/spotify_worker.py | 19 +++++++------------ core/tidal_worker.py | 23 +++++++++-------------- 11 files changed, 93 insertions(+), 121 deletions(-) diff --git a/core/audiodb_worker.py b/core/audiodb_worker.py index f299cadd..1588a919 100644 --- a/core/audiodb_worker.py +++ b/core/audiodb_worker.py @@ -38,7 +38,6 @@ class AudioDBWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Retry 'error' items after 7 days # Name matching threshold self.name_similarity_threshold = 0.80 @@ -197,46 +196,42 @@ class AudioDBWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_audiodb_id': row[3]} - # Priority 4: Retry 'not_found' or 'error' artists after retry_days/error_retry_days + # Priority 4: Retry 'not_found' artists after retry_days not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (audiodb_match_status = 'not_found' AND audiodb_last_attempted < ?) - OR (audiodb_match_status = 'error' AND audiodb_last_attempted < ?) + WHERE audiodb_match_status = 'not_found' AND audiodb_last_attempted < ? ORDER BY audiodb_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry 'not_found' or 'error' albums + # Priority 5: Retry 'not_found' albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name, ar.audiodb_id AS artist_audiodb_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.audiodb_match_status = 'not_found' AND a.audiodb_last_attempted < ?) - OR (a.audiodb_match_status = 'error' AND a.audiodb_last_attempted < ?) + WHERE a.audiodb_match_status = 'not_found' AND a.audiodb_last_attempted < ? ORDER BY a.audiodb_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_audiodb_id': row[3]} - # Priority 6: Retry 'not_found' or 'error' tracks + # Priority 6: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name, ar.audiodb_id AS artist_audiodb_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.audiodb_match_status = 'not_found' AND t.audiodb_last_attempted < ?) - OR (t.audiodb_match_status = 'error' AND t.audiodb_last_attempted < ?) + WHERE t.audiodb_match_status = 'not_found' AND t.audiodb_last_attempted < ? ORDER BY t.audiodb_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_audiodb_id': row[3]} diff --git a/core/deezer_worker.py b/core/deezer_worker.py index 184579f9..fa393d3d 100644 --- a/core/deezer_worker.py +++ b/core/deezer_worker.py @@ -38,7 +38,6 @@ class DeezerWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Retry 'error' items after 7 days # Name matching threshold self.name_similarity_threshold = 0.80 @@ -197,46 +196,42 @@ class DeezerWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_deezer_id': row[3]} - # Priority 4: Retry 'not_found' or 'error' artists after retry_days/error_retry_days + # Priority 4: Retry 'not_found' artists after retry_days not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (deezer_match_status = 'not_found' AND deezer_last_attempted < ?) - OR (deezer_match_status = 'error' AND deezer_last_attempted < ?) + WHERE deezer_match_status = 'not_found' AND deezer_last_attempted < ? ORDER BY deezer_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry 'not_found' or 'error' albums + # Priority 5: Retry 'not_found' albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name, ar.deezer_id AS artist_deezer_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.deezer_match_status = 'not_found' AND a.deezer_last_attempted < ?) - OR (a.deezer_match_status = 'error' AND a.deezer_last_attempted < ?) + WHERE a.deezer_match_status = 'not_found' AND a.deezer_last_attempted < ? ORDER BY a.deezer_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_deezer_id': row[3]} - # Priority 6: Retry 'not_found' or 'error' tracks + # Priority 6: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name, ar.deezer_id AS artist_deezer_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.deezer_match_status = 'not_found' AND t.deezer_last_attempted < ?) - OR (t.deezer_match_status = 'error' AND t.deezer_last_attempted < ?) + WHERE t.deezer_match_status = 'not_found' AND t.deezer_last_attempted < ? ORDER BY t.deezer_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_deezer_id': row[3]} diff --git a/core/genius_worker.py b/core/genius_worker.py index 31e2a1b2..f1bfd7a9 100644 --- a/core/genius_worker.py +++ b/core/genius_worker.py @@ -45,7 +45,6 @@ class GeniusWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Name matching threshold self.name_similarity_threshold = 0.75 # Slightly lower — Genius titles often include featured artists @@ -200,32 +199,29 @@ class GeniusWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 3: Retry artists + # Priority 3: Retry 'not_found' artists not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (genius_match_status = 'not_found' AND genius_last_attempted < ?) - OR (genius_match_status = 'error' AND genius_last_attempted < ?) + WHERE genius_match_status = 'not_found' AND genius_last_attempted < ? ORDER BY genius_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 4: Retry tracks + # Priority 4: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.genius_match_status = 'not_found' AND t.genius_last_attempted < ?) - OR (t.genius_match_status = 'error' AND t.genius_last_attempted < ?) + WHERE t.genius_match_status = 'not_found' AND t.genius_last_attempted < ? ORDER BY t.genius_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} diff --git a/core/itunes_worker.py b/core/itunes_worker.py index 72843af3..698c4111 100644 --- a/core/itunes_worker.py +++ b/core/itunes_worker.py @@ -252,18 +252,17 @@ class iTunesWorker: if row: return {'type': 'track_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 6: Retry stale failures + # Priority 6: Retry stale not_found items only (errors don't auto-retry — + # they require a user-triggered full refresh to prevent infinite retry loops) not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (itunes_match_status = 'not_found' AND itunes_last_attempted < ?) - OR (itunes_match_status = 'error' AND itunes_last_attempted < ?) + WHERE itunes_match_status = 'not_found' AND itunes_last_attempted < ? ORDER BY itunes_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'artist', 'id': row[0], 'name': row[1]} @@ -272,11 +271,10 @@ class iTunesWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.itunes_match_status = 'not_found' AND a.itunes_last_attempted < ?) - OR (a.itunes_match_status = 'error' AND a.itunes_last_attempted < ?) + WHERE a.itunes_match_status = 'not_found' AND a.itunes_last_attempted < ? ORDER BY a.itunes_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} @@ -285,11 +283,10 @@ class iTunesWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.itunes_match_status = 'not_found' AND t.itunes_last_attempted < ?) - OR (t.itunes_match_status = 'error' AND t.itunes_last_attempted < ?) + WHERE t.itunes_match_status = 'not_found' AND t.itunes_last_attempted < ? ORDER BY t.itunes_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} diff --git a/core/lastfm_worker.py b/core/lastfm_worker.py index a8fc15dd..8a30d11b 100644 --- a/core/lastfm_worker.py +++ b/core/lastfm_worker.py @@ -211,46 +211,43 @@ class LastFMWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 4: Retry 'not_found' or 'error' artists + # Priority 4: Retry 'not_found' artists only (errors don't auto-retry — + # they require a user-triggered full refresh to prevent infinite retry loops) not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (lastfm_match_status = 'not_found' AND lastfm_last_attempted < ?) - OR (lastfm_match_status = 'error' AND lastfm_last_attempted < ?) + WHERE lastfm_match_status = 'not_found' AND lastfm_last_attempted < ? ORDER BY lastfm_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry albums + # Priority 5: Retry not_found albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.lastfm_match_status = 'not_found' AND a.lastfm_last_attempted < ?) - OR (a.lastfm_match_status = 'error' AND a.lastfm_last_attempted < ?) + WHERE a.lastfm_match_status = 'not_found' AND a.lastfm_last_attempted < ? ORDER BY a.lastfm_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 6: Retry tracks + # Priority 6: Retry not_found tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.lastfm_match_status = 'not_found' AND t.lastfm_last_attempted < ?) - OR (t.lastfm_match_status = 'error' AND t.lastfm_last_attempted < ?) + WHERE t.lastfm_match_status = 'not_found' AND t.lastfm_last_attempted < ? ORDER BY t.lastfm_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} diff --git a/core/musicbrainz_worker.py b/core/musicbrainz_worker.py index 3ca79b5f..579644e6 100644 --- a/core/musicbrainz_worker.py +++ b/core/musicbrainz_worker.py @@ -34,7 +34,6 @@ class MusicBrainzWorker: # Retry configuration self.retry_days = 30 # Retry 'not_found' items after 30 days - self.error_retry_days = 7 # Retry 'error' items after 7 days logger.info("MusicBrainz background worker initialized") @@ -201,46 +200,42 @@ class MusicBrainzWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 4: Retry 'not_found' or 'error' artists after retry_days/error_retry_days + # Priority 4: Retry 'not_found' artists after retry_days not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (musicbrainz_match_status = 'not_found' AND musicbrainz_last_attempted < ?) - OR (musicbrainz_match_status = 'error' AND musicbrainz_last_attempted < ?) + WHERE musicbrainz_match_status = 'not_found' AND musicbrainz_last_attempted < ? ORDER BY musicbrainz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry 'not_found' or 'error' albums + # Priority 5: Retry 'not_found' albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.musicbrainz_match_status = 'not_found' AND a.musicbrainz_last_attempted < ?) - OR (a.musicbrainz_match_status = 'error' AND a.musicbrainz_last_attempted < ?) + WHERE a.musicbrainz_match_status = 'not_found' AND a.musicbrainz_last_attempted < ? ORDER BY a.musicbrainz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 6: Retry 'not_found' or 'error' tracks + # Priority 6: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.musicbrainz_match_status = 'not_found' AND t.musicbrainz_last_attempted < ?) - OR (t.musicbrainz_match_status = 'error' AND t.musicbrainz_last_attempted < ?) + WHERE t.musicbrainz_match_status = 'not_found' AND t.musicbrainz_last_attempted < ? ORDER BY t.musicbrainz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2]} diff --git a/core/qobuz_worker.py b/core/qobuz_worker.py index 73eec1a4..121f5ac9 100644 --- a/core/qobuz_worker.py +++ b/core/qobuz_worker.py @@ -38,7 +38,6 @@ class QobuzWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Name matching threshold self.name_similarity_threshold = 0.80 @@ -220,46 +219,42 @@ class QobuzWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_qobuz_id': row[3]} - # Priority 4: Retry 'not_found' or 'error' artists + # Priority 4: Retry 'not_found' artists not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (qobuz_match_status = 'not_found' AND qobuz_last_attempted < ?) - OR (qobuz_match_status = 'error' AND qobuz_last_attempted < ?) + WHERE qobuz_match_status = 'not_found' AND qobuz_last_attempted < ? ORDER BY qobuz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry 'not_found' or 'error' albums + # Priority 5: Retry 'not_found' albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name, ar.qobuz_id AS artist_qobuz_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.qobuz_match_status = 'not_found' AND a.qobuz_last_attempted < ?) - OR (a.qobuz_match_status = 'error' AND a.qobuz_last_attempted < ?) + WHERE a.qobuz_match_status = 'not_found' AND a.qobuz_last_attempted < ? ORDER BY a.qobuz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_qobuz_id': row[3]} - # Priority 6: Retry 'not_found' or 'error' tracks + # Priority 6: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name, ar.qobuz_id AS artist_qobuz_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.qobuz_match_status = 'not_found' AND t.qobuz_last_attempted < ?) - OR (t.qobuz_match_status = 'error' AND t.qobuz_last_attempted < ?) + WHERE t.qobuz_match_status = 'not_found' AND t.qobuz_last_attempted < ? ORDER BY t.qobuz_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_qobuz_id': row[3]} diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index d0824732..2ed9bae6 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -67,6 +67,8 @@ class MissingCoverArtJob(RepairJob): if context.report_progress: context.report_progress(phase=f'Searching artwork for {total} albums...', total=total) + spotify_skipped = False # Track if we've logged the rate limit skip + for i, row in enumerate(albums): if context.check_stop(): return result @@ -86,14 +88,23 @@ class MissingCoverArtJob(RepairJob): artwork_url = None + # Check Spotify rate limit at the top level — skip Spotify entirely if banned + spotify_available = not context.is_spotify_rate_limited() + if not spotify_available and not spotify_skipped: + logger.info("Spotify rate limited — skipping Spotify artwork lookups, using fallback only") + spotify_skipped = True + # Try to find artwork URL from APIs - if prefer_source == 'spotify': + if prefer_source == 'spotify' and spotify_available: artwork_url = self._try_spotify(spotify_album_id, title, artist_name, context) if not artwork_url: artwork_url = self._try_itunes(title, artist_name, context) + elif prefer_source == 'spotify' and not spotify_available: + # Spotify preferred but rate limited — use fallback only + artwork_url = self._try_itunes(title, artist_name, context) else: artwork_url = self._try_itunes(title, artist_name, context) - if not artwork_url: + if not artwork_url and spotify_available: artwork_url = self._try_spotify(spotify_album_id, title, artist_name, context) if artwork_url: diff --git a/core/repair_worker.py b/core/repair_worker.py index 9cdf07c3..052e4ab3 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -632,16 +632,17 @@ class RepairWorker: conn = self.db._get_connection() cursor = conn.cursor() - # Dedup check: skip if same finding already pending + # Dedup check: skip if same finding already exists (pending OR recently resolved) cursor.execute(""" SELECT id FROM repair_findings - WHERE job_id = ? AND finding_type = ? AND status = 'pending' + WHERE job_id = ? AND finding_type = ? + AND status IN ('pending', 'resolved') AND ((entity_type = ? AND entity_id = ?) OR (file_path = ? AND file_path IS NOT NULL)) LIMIT 1 """, (job_id, finding_type, entity_type, entity_id, file_path)) if cursor.fetchone(): - return # Already exists + return # Already exists or was already fixed cursor.execute(""" INSERT INTO repair_findings diff --git a/core/spotify_worker.py b/core/spotify_worker.py index 254f43da..aab9f6fa 100644 --- a/core/spotify_worker.py +++ b/core/spotify_worker.py @@ -45,7 +45,6 @@ class SpotifyWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Name matching threshold self.name_similarity_threshold = 0.80 @@ -287,18 +286,16 @@ class SpotifyWorker: if row: return {'type': 'track_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} - # Priority 6: Retry stale failures + # Priority 6: Retry stale 'not_found' failures not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (spotify_match_status = 'not_found' AND spotify_last_attempted < ?) - OR (spotify_match_status = 'error' AND spotify_last_attempted < ?) + WHERE spotify_match_status = 'not_found' AND spotify_last_attempted < ? ORDER BY spotify_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'artist', 'id': row[0], 'name': row[1]} @@ -307,11 +304,10 @@ class SpotifyWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.spotify_match_status = 'not_found' AND a.spotify_last_attempted < ?) - OR (a.spotify_match_status = 'error' AND a.spotify_last_attempted < ?) + WHERE a.spotify_match_status = 'not_found' AND a.spotify_last_attempted < ? ORDER BY a.spotify_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} @@ -320,11 +316,10 @@ class SpotifyWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.spotify_match_status = 'not_found' AND t.spotify_last_attempted < ?) - OR (t.spotify_match_status = 'error' AND t.spotify_last_attempted < ?) + WHERE t.spotify_match_status = 'not_found' AND t.spotify_last_attempted < ? ORDER BY t.spotify_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track_individual', 'id': row[0], 'name': row[1], 'artist': row[2]} diff --git a/core/tidal_worker.py b/core/tidal_worker.py index 1e99ebb9..464f0675 100644 --- a/core/tidal_worker.py +++ b/core/tidal_worker.py @@ -59,7 +59,6 @@ class TidalWorker: # Retry configuration self.retry_days = 30 - self.error_retry_days = 7 # Name matching threshold self.name_similarity_threshold = 0.80 @@ -232,46 +231,42 @@ class TidalWorker: if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_tidal_id': row[3]} - # Priority 4: Retry 'not_found' or 'error' artists + # Priority 4: Retry 'not_found' artists not_found_cutoff = datetime.now() - timedelta(days=self.retry_days) - error_cutoff = datetime.now() - timedelta(days=self.error_retry_days) cursor.execute(""" SELECT id, name FROM artists - WHERE (tidal_match_status = 'not_found' AND tidal_last_attempted < ?) - OR (tidal_match_status = 'error' AND tidal_last_attempted < ?) + WHERE tidal_match_status = 'not_found' AND tidal_last_attempted < ? ORDER BY tidal_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: logger.info(f"Retrying artist '{row[1]}' (last attempted before cutoff)") return {'type': 'artist', 'id': row[0], 'name': row[1]} - # Priority 5: Retry 'not_found' or 'error' albums + # Priority 5: Retry 'not_found' albums cursor.execute(""" SELECT a.id, a.title, ar.name AS artist_name, ar.tidal_id AS artist_tidal_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE (a.tidal_match_status = 'not_found' AND a.tidal_last_attempted < ?) - OR (a.tidal_match_status = 'error' AND a.tidal_last_attempted < ?) + WHERE a.tidal_match_status = 'not_found' AND a.tidal_last_attempted < ? ORDER BY a.tidal_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'album', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_tidal_id': row[3]} - # Priority 6: Retry 'not_found' or 'error' tracks + # Priority 6: Retry 'not_found' tracks cursor.execute(""" SELECT t.id, t.title, ar.name AS artist_name, ar.tidal_id AS artist_tidal_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE (t.tidal_match_status = 'not_found' AND t.tidal_last_attempted < ?) - OR (t.tidal_match_status = 'error' AND t.tidal_last_attempted < ?) + WHERE t.tidal_match_status = 'not_found' AND t.tidal_last_attempted < ? ORDER BY t.tidal_last_attempted ASC LIMIT 1 - """, (not_found_cutoff, error_cutoff)) + """, (not_found_cutoff,)) row = cursor.fetchone() if row: return {'type': 'track', 'id': row[0], 'name': row[1], 'artist': row[2], 'artist_tidal_id': row[3]}