diff --git a/core/repair_jobs/album_completeness.py b/core/repair_jobs/album_completeness.py index 227a9399..69686b60 100644 --- a/core/repair_jobs/album_completeness.py +++ b/core/repair_jobs/album_completeness.py @@ -90,7 +90,7 @@ class AlbumCompletenessJob(RepairJob): # If we don't know the expected track count, try to get it from API expected_total = db_track_count - if not expected_total and context.spotify_client: + if not expected_total and context.spotify_client and not context.is_spotify_rate_limited(): try: album_data = context.spotify_client.get_album(spotify_album_id) if album_data: @@ -106,7 +106,7 @@ class AlbumCompletenessJob(RepairJob): # Album is incomplete — try to find which tracks are missing missing_tracks = [] - if context.spotify_client: + if context.spotify_client and not context.is_spotify_rate_limited(): try: api_tracks = context.spotify_client.get_album_tracks(spotify_album_id) if api_tracks and 'items' in api_tracks: diff --git a/core/repair_jobs/base.py b/core/repair_jobs/base.py index ce35cd09..41178780 100644 --- a/core/repair_jobs/base.py +++ b/core/repair_jobs/base.py @@ -41,6 +41,18 @@ class JobContext: """Return True if the worker should stop.""" return self.should_stop() if self.should_stop else False + def is_spotify_rate_limited(self) -> bool: + """Check if Spotify is currently under a global rate limit ban. + + Jobs should call this before making Spotify API calls in their + scan loops to avoid churning through items uselessly. + """ + try: + from core.spotify_client import SpotifyClient + return SpotifyClient.is_rate_limited() + except Exception: + return False + def wait_if_paused(self): """Block until unpaused or stopped. Returns True if should stop.""" import time diff --git a/core/repair_jobs/metadata_gap_filler.py b/core/repair_jobs/metadata_gap_filler.py index e9b0b0c9..b2186ded 100644 --- a/core/repair_jobs/metadata_gap_filler.py +++ b/core/repair_jobs/metadata_gap_filler.py @@ -109,7 +109,7 @@ class MetadataGapFillerJob(RepairJob): found_fields = {} # Try Spotify enrichment first (most reliable for ISRC) - if spotify_track_id and context.spotify_client: + if spotify_track_id and context.spotify_client and not context.is_spotify_rate_limited(): try: track_data = context.spotify_client.get_track_details(spotify_track_id) if track_data: diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index 6edb4fdf..d0824732 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -147,6 +147,9 @@ class MissingCoverArtJob(RepairJob): return None try: + if context.is_spotify_rate_limited(): + return None + # If we have a Spotify album ID, fetch directly if spotify_album_id and client.is_spotify_authenticated(): album_data = client.get_album(spotify_album_id) diff --git a/core/repair_jobs/track_number_repair.py b/core/repair_jobs/track_number_repair.py index 60a1d91b..1d706af4 100644 --- a/core/repair_jobs/track_number_repair.py +++ b/core/repair_jobs/track_number_repair.py @@ -314,7 +314,7 @@ class TrackNumberRepairJob(RepairJob): # Fallback 3: Spotify track ID → discover album ID client = context.spotify_client - if spotify_track_id and client and client.is_spotify_authenticated(): + if spotify_track_id and client and client.is_spotify_authenticated() and not context.is_spotify_rate_limited(): try: track_details = client.get_track_details(spotify_track_id) if track_details and track_details.get('album', {}).get('id'): @@ -328,7 +328,7 @@ class TrackNumberRepairJob(RepairJob): logger.debug("Spotify track lookup failed for %s: %s", spotify_track_id, e) # Fallback 4: Search Spotify/iTunes by album name + artist - if album_name and client: + if album_name and client and not context.is_spotify_rate_limited(): try: query = f"{artist_name} {album_name}" if artist_name else album_name results = client.search_albums(query, limit=5) @@ -1031,7 +1031,7 @@ def _get_album_tracklist(album_id: str, context: JobContext, # Try Spotify first client = context.spotify_client - if client and client.is_spotify_authenticated(): + if client and client.is_spotify_authenticated() and not context.is_spotify_rate_limited(): try: data = client.get_album_tracks(album_id) if data and 'items' in data and data['items']: