Similar Artists worker: DB-backed stats (orb matches modal) + default on
- get_stats now reports PERSISTENT counts from the DB (matched/not_found/pending
+ a progress.artists breakdown) instead of in-memory session counters, so the
dashboard orb tooltip and the Manage modal agree (was showing 0 vs 14 after a
restart) and it survives restarts — same approach as the other workers.
- Orb tooltip reads progress.artists ('Artists: 14 / 15 (93%)') like the rest.
- Worker now defaults to ON (running) instead of opt-in-paused; still honors a
saved pause across restarts. It self-paces (~3s/artist) and backs off on
MusicMap outages, so the orb spins/active like the others when there's work.
10 seam tests pass.
This commit is contained in:
parent
d70d410f6f
commit
8ba20f7c49
3 changed files with 48 additions and 22 deletions
|
|
@ -167,12 +167,17 @@ class SimilarArtistsWorker:
|
|||
self.paused = False
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
try:
|
||||
self.stats['pending'] = self._count_pending()
|
||||
except Exception:
|
||||
pass
|
||||
# Report PERSISTENT counts from the DB (not the in-memory session
|
||||
# counters), so the dashboard orb and the Manage modal always agree and
|
||||
# survive restarts — same approach as the other enrichment workers.
|
||||
c = self._db_counts()
|
||||
self.stats = {
|
||||
'matched': c['matched'], 'not_found': c['not_found'],
|
||||
'pending': c['pending'], 'errors': c['error'],
|
||||
}
|
||||
total = c['total']
|
||||
is_running = self.running and (self.thread is not None and self.thread.is_alive())
|
||||
is_idle = is_running and not self.paused and self.stats['pending'] == 0 and self.current_item is None
|
||||
is_idle = is_running and not self.paused and c['pending'] == 0 and self.current_item is None
|
||||
return {
|
||||
'enabled': True,
|
||||
'running': is_running and not self.paused,
|
||||
|
|
@ -180,7 +185,14 @@ class SimilarArtistsWorker:
|
|||
'idle': is_idle,
|
||||
'current_item': self.current_item,
|
||||
'stats': self.stats.copy(),
|
||||
'progress': {},
|
||||
# Artist-only progress (no album/track phases) so the orb tooltip can
|
||||
# show "matched / total (percent%)" like every other worker.
|
||||
'progress': {
|
||||
'artists': {
|
||||
'matched': c['matched'], 'total': total,
|
||||
'percent': int(round(c['matched'] / total * 100)) if total else 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# ── worker loop ────────────────────────────────────────────────────────
|
||||
|
|
@ -284,13 +296,27 @@ class SimilarArtistsWorker:
|
|||
logger.debug("Similar Artists _mark failed for %s: %s", artist_id, exc)
|
||||
|
||||
def _count_pending(self) -> int:
|
||||
return self._db_counts()['pending']
|
||||
|
||||
def _db_counts(self) -> Dict[str, int]:
|
||||
"""Persistent tallies over the worker's universe (source-matched library
|
||||
artists): matched / not_found / error / pending(NULL) / total."""
|
||||
out = {'matched': 0, 'not_found': 0, 'error': 0, 'pending': 0, 'total': 0}
|
||||
try:
|
||||
conn = self.db._get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"SELECT COUNT(*) FROM artists WHERE similar_artists_match_status IS NULL "
|
||||
f"AND {self._has_source_id_clause()}"
|
||||
)
|
||||
return int(cursor.fetchone()[0] or 0)
|
||||
except Exception:
|
||||
return 0
|
||||
cursor.execute(f"""
|
||||
SELECT
|
||||
SUM(CASE WHEN similar_artists_match_status = 'matched' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN similar_artists_match_status = 'not_found' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN similar_artists_match_status = 'error' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN similar_artists_match_status IS NULL THEN 1 ELSE 0 END),
|
||||
COUNT(*)
|
||||
FROM artists WHERE {self._has_source_id_clause()}
|
||||
""")
|
||||
row = cursor.fetchone() or (0, 0, 0, 0, 0)
|
||||
out.update(matched=int(row[0] or 0), not_found=int(row[1] or 0),
|
||||
error=int(row[2] or 0), pending=int(row[3] or 0), total=int(row[4] or 0))
|
||||
except Exception as exc:
|
||||
logger.debug("Similar Artists _db_counts failed: %s", exc)
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -32891,18 +32891,18 @@ except Exception as e:
|
|||
|
||||
# --- Similar Artists Worker Initialization ---
|
||||
# Fills the similar_artists table for LIBRARY artists (the watchlist scanner only
|
||||
# covers watchlist artists). Opt-in by default like Amazon: it leans on MusicMap
|
||||
# (a scraped, outage-prone source) and does per-candidate metadata-source
|
||||
# searches across the whole library, so it stays paused until the user enables it.
|
||||
# covers watchlist artists). Runs by default (like the metadata workers); it
|
||||
# self-paces (~3s/artist) and backs off on MusicMap outages. Respects a saved
|
||||
# pause choice across restarts.
|
||||
similar_artists_worker = None
|
||||
try:
|
||||
from core.similar_artists_worker import SimilarArtistsWorker
|
||||
similar_artists_db = MusicDatabase()
|
||||
similar_artists_worker = SimilarArtistsWorker(database=similar_artists_db)
|
||||
similar_artists_worker.start()
|
||||
if config_manager.get('similar_artists_enrichment_paused', True):
|
||||
if config_manager.get('similar_artists_enrichment_paused', False):
|
||||
similar_artists_worker.pause()
|
||||
logger.info("Similar Artists worker initialized (paused — enable it to populate library similars)")
|
||||
logger.info("Similar Artists worker initialized (paused — restored from config)")
|
||||
else:
|
||||
logger.info("Similar Artists worker initialized and started")
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1351,10 +1351,10 @@ function updateSimilarArtistsEnrichmentStatusFromData(data) {
|
|||
else if (data.current_item) tCurrent.textContent = `Now: ${data.current_item}`;
|
||||
else tCurrent.textContent = 'No active matches';
|
||||
}
|
||||
// This worker has no artist/album/track phases — show its matched/pending tally.
|
||||
if (tProgress && data.stats) {
|
||||
const s = data.stats;
|
||||
tProgress.textContent = `${s.matched || 0} matched · ${s.pending || 0} pending`;
|
||||
// DB-backed artist progress (matches the Manage modal exactly).
|
||||
if (tProgress) {
|
||||
const a = (data.progress && data.progress.artists) || {};
|
||||
tProgress.textContent = `Artists: ${a.matched || 0} / ${a.total || 0} (${a.percent || 0}%)`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue