Apply manual match protection to all enrichment workers (#226)
The original #221 fix only covered Genius and AudioDB. All other workers (Spotify, iTunes, Last.fm, MusicBrainz, Deezer, Tidal, Qobuz) had the same bug: enrichment overwrites manual match status to not_found when name search fails. Each worker now checks for an existing service ID before searching by name and returns early if one exists, preserving the manual match.
This commit is contained in:
parent
6c4d8d9348
commit
1a0fd8b95e
7 changed files with 213 additions and 0 deletions
|
|
@ -337,8 +337,32 @@ class DeezerWorker:
|
|||
except Exception as e2:
|
||||
logger.error(f"Error updating item status: {e2}")
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a deezer_id (e.g. from manual match)."""
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
table = table_map.get(entity_type)
|
||||
if not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT deezer_id FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, artist_id: int, artist_name: str):
|
||||
"""Process an artist: search Deezer, verify, store metadata"""
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Deezer ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_artist(artist_name)
|
||||
if result:
|
||||
result_name = result.get('name', '')
|
||||
|
|
@ -357,6 +381,11 @@ class DeezerWorker:
|
|||
|
||||
def _process_album(self, album_id: int, album_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process an album: search Deezer, verify, fetch full details, store metadata"""
|
||||
existing_id = self._get_existing_id('album', album_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Deezer ID for album '{album_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_album(artist_name, album_name)
|
||||
if result:
|
||||
result_name = result.get('title', '')
|
||||
|
|
@ -397,6 +426,11 @@ class DeezerWorker:
|
|||
|
||||
def _process_track(self, track_id: int, track_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process a track: search Deezer, verify, fetch full details for BPM, store metadata"""
|
||||
existing_id = self._get_existing_id('track', track_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Deezer ID for track '{track_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_track(artist_name, track_name)
|
||||
if result:
|
||||
result_name = result.get('title', '')
|
||||
|
|
|
|||
|
|
@ -338,10 +338,36 @@ class iTunesWorker:
|
|||
|
||||
# ── Artist processing ──────────────────────────────────────────────
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has an itunes_artist_id/itunes_album_id/itunes_track_id."""
|
||||
col_map = {'artist': 'itunes_artist_id', 'album': 'itunes_album_id', 'track': 'itunes_track_id'}
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
col = col_map.get(entity_type)
|
||||
table = table_map.get(entity_type)
|
||||
if not col or not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT {col} FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, item: Dict[str, Any]):
|
||||
artist_id = item['id']
|
||||
artist_name = item['name']
|
||||
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing iTunes ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
results = self.client.search_artists(artist_name, limit=5)
|
||||
if not results:
|
||||
self._mark_status('artist', artist_id, 'not_found')
|
||||
|
|
|
|||
|
|
@ -302,8 +302,32 @@ class LastFMWorker:
|
|||
except Exception as e2:
|
||||
logger.error(f"Error updating item status: {e2}")
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a lastfm_id (e.g. from manual match)."""
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
table = table_map.get(entity_type)
|
||||
if not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT lastfm_id FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, artist_id: int, artist_name: str):
|
||||
"""Process an artist: get full info from Last.fm"""
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Last.fm ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
# Use get_artist_info for detailed data (includes stats, bio, tags, similar)
|
||||
result = self.client.get_artist_info(artist_name)
|
||||
if result:
|
||||
|
|
@ -323,6 +347,11 @@ class LastFMWorker:
|
|||
|
||||
def _process_album(self, album_id: int, album_name: str, artist_name: str):
|
||||
"""Process an album: get full info from Last.fm"""
|
||||
existing_id = self._get_existing_id('album', album_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Last.fm ID for album '{album_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.get_album_info(artist_name, album_name)
|
||||
if result:
|
||||
result_name = result.get('name', '')
|
||||
|
|
@ -341,6 +370,11 @@ class LastFMWorker:
|
|||
|
||||
def _process_track(self, track_id: int, track_name: str, artist_name: str):
|
||||
"""Process a track: get full info from Last.fm"""
|
||||
existing_id = self._get_existing_id('track', track_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Last.fm ID for track '{track_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.get_track_info(artist_name, track_name)
|
||||
if result:
|
||||
result_name = result.get('name', '')
|
||||
|
|
|
|||
|
|
@ -249,6 +249,25 @@ class MusicBrainzWorker:
|
|||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a musicbrainz_id (e.g. from manual match)."""
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
table = table_map.get(entity_type)
|
||||
if not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT musicbrainz_id FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_item(self, item: Dict[str, Any]):
|
||||
"""Process a single item (artist, album, or track)"""
|
||||
try:
|
||||
|
|
@ -258,6 +277,12 @@ class MusicBrainzWorker:
|
|||
|
||||
logger.debug(f"Processing {item_type} #{item_id}: {item_name}")
|
||||
|
||||
# Preserve existing manual matches
|
||||
existing_id = self._get_existing_id(item_type, item_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing MusicBrainz ID for {item_type} '{item_name}': {existing_id}")
|
||||
return
|
||||
|
||||
if item_type == 'artist':
|
||||
result = self.mb_service.match_artist(item_name)
|
||||
if result and result.get('mbid'):
|
||||
|
|
|
|||
|
|
@ -360,8 +360,32 @@ class QobuzWorker:
|
|||
except Exception as e2:
|
||||
logger.error(f"Error updating item status: {e2}")
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a qobuz_id (e.g. from manual match)."""
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
table = table_map.get(entity_type)
|
||||
if not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT qobuz_id FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, artist_id: int, artist_name: str):
|
||||
"""Process an artist: search Qobuz, verify, store metadata"""
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Qobuz ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_artist(artist_name)
|
||||
|
||||
if result:
|
||||
|
|
@ -398,6 +422,11 @@ class QobuzWorker:
|
|||
|
||||
def _process_album(self, album_id: int, album_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process an album: search Qobuz, verify, fetch full details, store metadata"""
|
||||
existing_id = self._get_existing_id('album', album_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Qobuz ID for album '{album_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_album(artist_name, album_name)
|
||||
|
||||
if result:
|
||||
|
|
@ -448,6 +477,11 @@ class QobuzWorker:
|
|||
|
||||
def _process_track(self, track_id: int, track_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process a track: search Qobuz, verify, fetch full details, store metadata"""
|
||||
existing_id = self._get_existing_id('track', track_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Qobuz ID for track '{track_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_track(artist_name, track_name)
|
||||
|
||||
if result:
|
||||
|
|
|
|||
|
|
@ -423,10 +423,36 @@ class SpotifyWorker:
|
|||
|
||||
# ── Artist processing ──────────────────────────────────────────────
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a spotify_artist_id/spotify_album_id/spotify_track_id."""
|
||||
col_map = {'artist': 'spotify_artist_id', 'album': 'spotify_album_id', 'track': 'spotify_track_id'}
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
col = col_map.get(entity_type)
|
||||
table = table_map.get(entity_type)
|
||||
if not col or not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT {col} FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, item: Dict[str, Any]):
|
||||
artist_id = item['id']
|
||||
artist_name = item['name']
|
||||
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Spotify ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
results = self.client.search_artists(artist_name, limit=5)
|
||||
if not results:
|
||||
self._mark_status('artist', artist_id, 'not_found')
|
||||
|
|
|
|||
|
|
@ -373,8 +373,32 @@ class TidalWorker:
|
|||
except Exception as e2:
|
||||
logger.error(f"Error updating item status: {e2}")
|
||||
|
||||
def _get_existing_id(self, entity_type: str, entity_id: int) -> Optional[str]:
|
||||
"""Check if an entity already has a tidal_id (e.g. from manual match)."""
|
||||
table_map = {'artist': 'artists', 'album': 'albums', 'track': 'tracks'}
|
||||
table = table_map.get(entity_type)
|
||||
if not table:
|
||||
return None
|
||||
conn = None
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT tidal_id FROM {table} WHERE id = ?", (entity_id,))
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] else None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _process_artist(self, artist_id: int, artist_name: str):
|
||||
"""Process an artist: search Tidal, verify, store metadata"""
|
||||
existing_id = self._get_existing_id('artist', artist_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Tidal ID for artist '{artist_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_artist(artist_name)
|
||||
if result:
|
||||
result_name = result.get('name', '')
|
||||
|
|
@ -407,6 +431,11 @@ class TidalWorker:
|
|||
|
||||
def _process_album(self, album_id: int, album_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process an album: search Tidal, verify, fetch full details, store metadata"""
|
||||
existing_id = self._get_existing_id('album', album_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Tidal ID for album '{album_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_album(artist_name, album_name)
|
||||
if result:
|
||||
result_name = result.get('title', '')
|
||||
|
|
@ -450,6 +479,11 @@ class TidalWorker:
|
|||
|
||||
def _process_track(self, track_id: int, track_name: str, artist_name: str, item: Dict[str, Any]):
|
||||
"""Process a track: search Tidal, verify, fetch full details, store metadata"""
|
||||
existing_id = self._get_existing_id('track', track_id)
|
||||
if existing_id:
|
||||
logger.debug(f"Preserving existing Tidal ID for track '{track_name}': {existing_id}")
|
||||
return
|
||||
|
||||
result = self.client.search_track(artist_name, track_name)
|
||||
if result:
|
||||
result_name = result.get('title', '')
|
||||
|
|
|
|||
Loading…
Reference in a new issue