from dataclasses import dataclass from datetime import date, datetime import psycopg from lyra_worker.browser import MbBrowser _TRUE = {"1", "true", "yes", "on"} @dataclass(frozen=True) class MonitorConfig: enabled: bool = False poll_interval_hours: int = 24 retry_interval_hours: int = 6 quality_cutoff: int = 2 upgrade_window_days: int = 14 @classmethod def from_config(cls, config: dict) -> "MonitorConfig": def _int(key: str, default: int) -> int: try: return int(config[key]) except (KeyError, TypeError, ValueError): return default return cls( enabled=str(config.get("monitor.enabled", "")).strip().lower() in _TRUE, poll_interval_hours=_int("monitor.pollIntervalHours", 24), retry_interval_hours=_int("monitor.retryIntervalHours", 6), quality_cutoff=_int("monitor.qualityCutoff", 2), upgrade_window_days=_int("monitor.upgradeWindowDays", 14), ) def _is_new(first_release_date: str, monitor_from: datetime) -> bool: """A release is 'new going forward' if its first-release date is after monitorFrom.""" if not first_release_date: return False try: frd = date.fromisoformat(first_release_date[:10]) except ValueError: return False return frd > monitor_from.date() def discover(conn: psycopg.Connection, browser: MbBrowser, cfg: MonitorConfig) -> int: """Poll each due watched artist; insert newly-seen release-groups. Returns #rows inserted.""" with conn.cursor() as cur: cur.execute( 'SELECT id, mbid, name, "autoMonitorFuture", "monitorFrom" FROM "WatchedArtist" ' 'WHERE "lastPolledAt" IS NULL ' ' OR "lastPolledAt" < now() - make_interval(hours => %s)', (cfg.poll_interval_hours,), ) artists = cur.fetchall() inserted = 0 for artist_id, mbid, name, auto_future, monitor_from in artists: for rg in browser.browse_release_groups(mbid): monitored = bool(auto_future) and _is_new(rg.first_release_date, monitor_from) with conn.cursor() as cur: cur.execute( 'INSERT INTO "MonitoredRelease" (id, "watchedArtistId", "artistMbid", ' '"artistName", "rgMbid", album, "primaryType", "secondaryTypes", ' '"firstReleaseDate", monitored, state, "createdAt") ' "VALUES (gen_random_uuid()::text, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'wanted', now()) " 'ON CONFLICT ("rgMbid") DO NOTHING', (artist_id, mbid, name, rg.rg_mbid, rg.title, rg.primary_type or None, list(rg.secondary_types), rg.first_release_date or None, monitored), ) inserted += cur.rowcount with conn.cursor() as cur: cur.execute('UPDATE "WatchedArtist" SET "lastPolledAt" = now() WHERE id = %s', (artist_id,)) conn.commit() return inserted