Add Discogs to watchlist — column, backfill, matching
- Add discogs_artist_id column to watchlist_artists table (migration) - Add discogs_artist_id to WatchlistArtist dataclass - Add to get_watchlist_artists optional_columns and constructor - Add update_watchlist_discogs_id DB method - Backfill loop includes Discogs when token is configured - Add _match_to_discogs for cross-provider artist matching - Backfill maps updated: id_attr, match_fn, update_fn all include discogs
This commit is contained in:
parent
58c3e589b6
commit
82f9b84e5b
2 changed files with 54 additions and 3 deletions
|
|
@ -609,6 +609,12 @@ class WatchlistScanner:
|
||||||
providers_to_backfill = ['itunes', 'deezer']
|
providers_to_backfill = ['itunes', 'deezer']
|
||||||
if self.spotify_client and self.spotify_client.is_spotify_authenticated():
|
if self.spotify_client and self.spotify_client.is_spotify_authenticated():
|
||||||
providers_to_backfill.append('spotify')
|
providers_to_backfill.append('spotify')
|
||||||
|
try:
|
||||||
|
from config.settings import config_manager as _cfg
|
||||||
|
if _cfg.get('discogs.token', ''):
|
||||||
|
providers_to_backfill.append('discogs')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
for provider in providers_to_backfill:
|
for provider in providers_to_backfill:
|
||||||
try:
|
try:
|
||||||
|
|
@ -979,6 +985,7 @@ class WatchlistScanner:
|
||||||
'spotify': 'spotify_artist_id',
|
'spotify': 'spotify_artist_id',
|
||||||
'itunes': 'itunes_artist_id',
|
'itunes': 'itunes_artist_id',
|
||||||
'deezer': 'deezer_artist_id',
|
'deezer': 'deezer_artist_id',
|
||||||
|
'discogs': 'discogs_artist_id',
|
||||||
}.get(provider)
|
}.get(provider)
|
||||||
|
|
||||||
if not id_attr:
|
if not id_attr:
|
||||||
|
|
@ -997,12 +1004,14 @@ class WatchlistScanner:
|
||||||
'spotify': self._match_to_spotify,
|
'spotify': self._match_to_spotify,
|
||||||
'itunes': self._match_to_itunes,
|
'itunes': self._match_to_itunes,
|
||||||
'deezer': self._match_to_deezer,
|
'deezer': self._match_to_deezer,
|
||||||
|
'discogs': self._match_to_discogs,
|
||||||
}.get(provider)
|
}.get(provider)
|
||||||
|
|
||||||
update_fn = {
|
update_fn = {
|
||||||
'spotify': self.database.update_watchlist_spotify_id,
|
'spotify': self.database.update_watchlist_spotify_id,
|
||||||
'itunes': self.database.update_watchlist_itunes_id,
|
'itunes': self.database.update_watchlist_itunes_id,
|
||||||
'deezer': getattr(self.database, 'update_watchlist_deezer_id', None),
|
'deezer': getattr(self.database, 'update_watchlist_deezer_id', None),
|
||||||
|
'discogs': getattr(self.database, 'update_watchlist_discogs_id', None),
|
||||||
}.get(provider)
|
}.get(provider)
|
||||||
|
|
||||||
if not match_fn or not update_fn:
|
if not match_fn or not update_fn:
|
||||||
|
|
@ -1150,6 +1159,17 @@ class WatchlistScanner:
|
||||||
logger.warning(f"Could not match {artist_name} to Deezer: {e}")
|
logger.warning(f"Could not match {artist_name} to Deezer: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _match_to_discogs(self, artist_name: str) -> Optional[str]:
|
||||||
|
"""Match artist name to Discogs ID using fuzzy name comparison."""
|
||||||
|
try:
|
||||||
|
from core.discogs_client import DiscogsClient
|
||||||
|
client = DiscogsClient()
|
||||||
|
results = client.search_artists(artist_name, limit=5)
|
||||||
|
return self._best_artist_match(results, artist_name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not match {artist_name} to Discogs: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_lookback_period_setting(self) -> str:
|
def _get_lookback_period_setting(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the discovery lookback period setting from database.
|
Get the discovery lookback period setting from database.
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ class WatchlistArtist:
|
||||||
image_url: Optional[str] = None
|
image_url: Optional[str] = None
|
||||||
itunes_artist_id: Optional[str] = None # Cross-provider support
|
itunes_artist_id: Optional[str] = None # Cross-provider support
|
||||||
deezer_artist_id: Optional[str] = None # Cross-provider support
|
deezer_artist_id: Optional[str] = None # Cross-provider support
|
||||||
|
discogs_artist_id: Optional[str] = None # Cross-provider support
|
||||||
include_albums: bool = True
|
include_albums: bool = True
|
||||||
include_eps: bool = True
|
include_eps: bool = True
|
||||||
include_singles: bool = True
|
include_singles: bool = True
|
||||||
|
|
@ -1272,6 +1273,10 @@ class MusicDatabase:
|
||||||
cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN deezer_artist_id TEXT")
|
cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN deezer_artist_id TEXT")
|
||||||
logger.info("Added deezer_artist_id column to watchlist_artists table for cross-provider support")
|
logger.info("Added deezer_artist_id column to watchlist_artists table for cross-provider support")
|
||||||
|
|
||||||
|
if 'discogs_artist_id' not in columns:
|
||||||
|
cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN discogs_artist_id TEXT")
|
||||||
|
logger.info("Added discogs_artist_id column to watchlist_artists table for cross-provider support")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
|
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
|
||||||
# Don't raise - this is a migration, database can still function
|
# Don't raise - this is a migration, database can still function
|
||||||
|
|
@ -6444,7 +6449,7 @@ class MusicDatabase:
|
||||||
|
|
||||||
# Check if artist already exists by name (case-insensitive) for this profile
|
# Check if artist already exists by name (case-insensitive) for this profile
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id
|
SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id, discogs_artist_id
|
||||||
FROM watchlist_artists
|
FROM watchlist_artists
|
||||||
WHERE LOWER(artist_name) = LOWER(?) AND profile_id = ?
|
WHERE LOWER(artist_name) = LOWER(?) AND profile_id = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
|
@ -6457,7 +6462,7 @@ class MusicDatabase:
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
# Artist already on watchlist — update with new source ID if missing
|
# Artist already on watchlist — update with new source ID if missing
|
||||||
col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id'}
|
col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id', 'discogs': 'discogs_artist_id'}
|
||||||
col = col_map.get(source)
|
col = col_map.get(source)
|
||||||
if col and not existing[col]:
|
if col and not existing[col]:
|
||||||
cursor.execute(f"""
|
cursor.execute(f"""
|
||||||
|
|
@ -6486,6 +6491,13 @@ class MusicDatabase:
|
||||||
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)
|
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)
|
||||||
""", (artist_id, artist_name, profile_id))
|
""", (artist_id, artist_name, profile_id))
|
||||||
logger.info(f"Added artist '{artist_name}' to watchlist (iTunes ID: {artist_id}, profile: {profile_id})")
|
logger.info(f"Added artist '{artist_name}' to watchlist (iTunes ID: {artist_id}, profile: {profile_id})")
|
||||||
|
elif source == 'discogs':
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO watchlist_artists
|
||||||
|
(discogs_artist_id, artist_name, date_added, updated_at, profile_id)
|
||||||
|
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)
|
||||||
|
""", (artist_id, artist_name, profile_id))
|
||||||
|
logger.info(f"Added artist '{artist_name}' to watchlist (Discogs ID: {artist_id}, profile: {profile_id})")
|
||||||
else:
|
else:
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO watchlist_artists
|
INSERT INTO watchlist_artists
|
||||||
|
|
@ -6572,7 +6584,7 @@ class MusicDatabase:
|
||||||
# Build SELECT query based on existing columns
|
# Build SELECT query based on existing columns
|
||||||
base_columns = ['id', 'spotify_artist_id', 'artist_name', 'date_added',
|
base_columns = ['id', 'spotify_artist_id', 'artist_name', 'date_added',
|
||||||
'last_scan_timestamp', 'created_at', 'updated_at']
|
'last_scan_timestamp', 'created_at', 'updated_at']
|
||||||
optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'include_albums', 'include_eps', 'include_singles',
|
optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'discogs_artist_id', 'include_albums', 'include_eps', 'include_singles',
|
||||||
'include_live', 'include_remixes', 'include_acoustic', 'include_compilations',
|
'include_live', 'include_remixes', 'include_acoustic', 'include_compilations',
|
||||||
'include_instrumentals', 'lookback_days']
|
'include_instrumentals', 'lookback_days']
|
||||||
|
|
||||||
|
|
@ -6600,6 +6612,7 @@ class MusicDatabase:
|
||||||
image_url = row['image_url'] if 'image_url' in existing_columns else None
|
image_url = row['image_url'] if 'image_url' in existing_columns else None
|
||||||
itunes_artist_id = row['itunes_artist_id'] if 'itunes_artist_id' in existing_columns else None
|
itunes_artist_id = row['itunes_artist_id'] if 'itunes_artist_id' in existing_columns else None
|
||||||
deezer_artist_id = row['deezer_artist_id'] if 'deezer_artist_id' in existing_columns else None
|
deezer_artist_id = row['deezer_artist_id'] if 'deezer_artist_id' in existing_columns else None
|
||||||
|
discogs_artist_id = row['discogs_artist_id'] if 'discogs_artist_id' in existing_columns else None
|
||||||
include_albums = bool(row['include_albums']) if 'include_albums' in existing_columns else True
|
include_albums = bool(row['include_albums']) if 'include_albums' in existing_columns else True
|
||||||
include_eps = bool(row['include_eps']) if 'include_eps' in existing_columns else True
|
include_eps = bool(row['include_eps']) if 'include_eps' in existing_columns else True
|
||||||
include_singles = bool(row['include_singles']) if 'include_singles' in existing_columns else True
|
include_singles = bool(row['include_singles']) if 'include_singles' in existing_columns else True
|
||||||
|
|
@ -6621,6 +6634,7 @@ class MusicDatabase:
|
||||||
image_url=image_url,
|
image_url=image_url,
|
||||||
itunes_artist_id=itunes_artist_id,
|
itunes_artist_id=itunes_artist_id,
|
||||||
deezer_artist_id=deezer_artist_id,
|
deezer_artist_id=deezer_artist_id,
|
||||||
|
discogs_artist_id=discogs_artist_id,
|
||||||
include_albums=include_albums,
|
include_albums=include_albums,
|
||||||
include_eps=include_eps,
|
include_eps=include_eps,
|
||||||
include_singles=include_singles,
|
include_singles=include_singles,
|
||||||
|
|
@ -6889,6 +6903,23 @@ class MusicDatabase:
|
||||||
logger.error(f"Error updating watchlist Deezer ID: {e}")
|
logger.error(f"Error updating watchlist Deezer ID: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update_watchlist_discogs_id(self, watchlist_id: int, discogs_id: str) -> bool:
|
||||||
|
"""Update the Discogs artist ID for a watchlist artist (cross-provider support)"""
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE watchlist_artists
|
||||||
|
SET discogs_artist_id = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
""", (discogs_id, watchlist_id))
|
||||||
|
conn.commit()
|
||||||
|
logger.info(f"Updated Discogs ID for watchlist artist {watchlist_id}: {discogs_id}")
|
||||||
|
return cursor.rowcount > 0
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating watchlist Discogs ID: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def update_watchlist_artist_itunes_id(self, spotify_artist_id: str, itunes_id: str) -> bool:
|
def update_watchlist_artist_itunes_id(self, spotify_artist_id: str, itunes_id: str) -> bool:
|
||||||
"""Update the iTunes artist ID for a watchlist artist by Spotify ID (for cross-provider caching)"""
|
"""Update the iTunes artist ID for a watchlist artist by Spotify ID (for cross-provider caching)"""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue