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
pull/253/head
Broque Thomas 2 months ago
parent d8217d66ba
commit 429306c7f3

@ -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]}

@ -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]}

@ -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]}

@ -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]}

@ -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]}

@ -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]}

@ -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]}

@ -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:

@ -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

@ -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]}

@ -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]}

Loading…
Cancel
Save