Raise Spotify API interval; pause enrichments
Increase Spotify client rate limit and reduce API contention during watchlist scans. Changes: - core/spotify_client.py: Bumped MIN_API_INTERVAL from 0.2s to 0.35s (~171 calls/min) to stay safely under Spotify's ~180/min limit. - web_server.py: In start_watchlist_scan and automatic scan flow, pause spotify_enrichment_worker and itunes_enrichment_worker before scanning (tracking with _enrichment_was_running/_itunes_enrichment_was_running) and resume them in finally blocks; added console prints for pause/resume. This prevents enrichment workers from contending for API quota during long scans. - webui/static/script.js: Improved enrichment status tooltip logic to prioritize explicit currentType and then fall back to completion-based inference with explicit branches for artists, albums, and tracks for clearer progress text. These changes aim to avoid API rate violations and make scan progress display more predictable.
This commit is contained in:
parent
317d5c1770
commit
1c34967fd3
3 changed files with 60 additions and 9 deletions
|
|
@ -13,7 +13,7 @@ logger = get_logger("spotify_client")
|
|||
# Global rate limiting variables
|
||||
_last_api_call_time = 0
|
||||
_api_call_lock = threading.Lock()
|
||||
MIN_API_INTERVAL = 0.2 # 200ms between API calls (more conservative to avoid bans)
|
||||
MIN_API_INTERVAL = 0.35 # 350ms between API calls (~171/min, under Spotify's ~180/min limit)
|
||||
|
||||
# Request queuing for burst handling
|
||||
import queue
|
||||
|
|
|
|||
|
|
@ -20496,6 +20496,8 @@ def start_watchlist_scan():
|
|||
|
||||
# Start the scan in a background thread
|
||||
def run_scan():
|
||||
_enrichment_was_running = False
|
||||
_itunes_enrichment_was_running = False
|
||||
try:
|
||||
global watchlist_scan_state, watchlist_auto_scanning, watchlist_auto_scanning_timestamp
|
||||
from core.watchlist_scanner import WatchlistScanner
|
||||
|
|
@ -20561,6 +20563,16 @@ def start_watchlist_scan():
|
|||
|
||||
scan_results = []
|
||||
|
||||
# Pause enrichment workers during scan to reduce API contention
|
||||
if spotify_enrichment_worker and not spotify_enrichment_worker.paused:
|
||||
spotify_enrichment_worker.pause()
|
||||
_enrichment_was_running = True
|
||||
print("⏸️ Paused Spotify enrichment worker during watchlist scan")
|
||||
if itunes_enrichment_worker and not itunes_enrichment_worker.paused:
|
||||
itunes_enrichment_worker.pause()
|
||||
_itunes_enrichment_was_running = True
|
||||
print("⏸️ Paused iTunes enrichment worker during watchlist scan")
|
||||
|
||||
# Dynamic delay calculation based on scan scope
|
||||
lookback_period = scanner._get_lookback_period_setting()
|
||||
is_full_discography = (lookback_period == 'all')
|
||||
|
|
@ -20843,6 +20855,14 @@ def start_watchlist_scan():
|
|||
watchlist_scan_state['error'] = str(e)
|
||||
|
||||
finally:
|
||||
# Resume enrichment workers if we paused them
|
||||
if _enrichment_was_running and spotify_enrichment_worker:
|
||||
spotify_enrichment_worker.resume()
|
||||
print("▶️ Resumed Spotify enrichment worker after watchlist scan")
|
||||
if _itunes_enrichment_was_running and itunes_enrichment_worker:
|
||||
itunes_enrichment_worker.resume()
|
||||
print("▶️ Resumed iTunes enrichment worker after watchlist scan")
|
||||
|
||||
# Always reset flag when scan completes (success or error)
|
||||
with watchlist_timer_lock:
|
||||
watchlist_auto_scanning = False
|
||||
|
|
@ -21236,6 +21256,9 @@ def _process_watchlist_scan_automatically():
|
|||
|
||||
print("🤖 [Auto-Watchlist] Timer triggered - starting automatic watchlist scan...")
|
||||
|
||||
_enrichment_was_running = False
|
||||
_itunes_enrichment_was_running = False
|
||||
|
||||
try:
|
||||
# CRITICAL FIX: Use smart stuck detection BEFORE acquiring lock
|
||||
# This prevents deadlock and handles stuck flags (2-hour timeout)
|
||||
|
|
@ -21326,6 +21349,16 @@ def _process_watchlist_scan_automatically():
|
|||
|
||||
scan_results = []
|
||||
|
||||
# Pause enrichment workers during scan to reduce API contention
|
||||
if spotify_enrichment_worker and not spotify_enrichment_worker.paused:
|
||||
spotify_enrichment_worker.pause()
|
||||
_enrichment_was_running = True
|
||||
print("⏸️ [Auto-Watchlist] Paused Spotify enrichment worker during scan")
|
||||
if itunes_enrichment_worker and not itunes_enrichment_worker.paused:
|
||||
itunes_enrichment_worker.pause()
|
||||
_itunes_enrichment_was_running = True
|
||||
print("⏸️ [Auto-Watchlist] Paused iTunes enrichment worker during scan")
|
||||
|
||||
# Dynamic delay calculation based on scan scope
|
||||
lookback_period = scanner._get_lookback_period_setting()
|
||||
is_full_discography = (lookback_period == 'all')
|
||||
|
|
@ -21595,6 +21628,14 @@ def _process_watchlist_scan_automatically():
|
|||
watchlist_scan_state['error'] = str(e)
|
||||
|
||||
finally:
|
||||
# Resume enrichment workers if we paused them
|
||||
if _enrichment_was_running and spotify_enrichment_worker:
|
||||
spotify_enrichment_worker.resume()
|
||||
print("▶️ [Auto-Watchlist] Resumed Spotify enrichment worker after scan")
|
||||
if _itunes_enrichment_was_running and itunes_enrichment_worker:
|
||||
itunes_enrichment_worker.resume()
|
||||
print("▶️ [Auto-Watchlist] Resumed iTunes enrichment worker after scan")
|
||||
|
||||
# Always reset flag and schedule next scan
|
||||
with watchlist_timer_lock:
|
||||
watchlist_auto_scanning = False
|
||||
|
|
|
|||
|
|
@ -36705,14 +36705,19 @@ async function updateSpotifyEnrichmentStatus() {
|
|||
const artistsComplete = artists.matched >= artists.total;
|
||||
const albumsComplete = albums.matched >= albums.total;
|
||||
|
||||
if (currentType === 'artist' || (!artistsComplete && !currentType)) {
|
||||
// Prioritize currentType over completion-based inference
|
||||
if (currentType === 'artist') {
|
||||
progressText = `Artists: ${artists.matched || 0} / ${artists.total || 0} (${artists.percent || 0}%)`;
|
||||
} else if (currentType.includes('album') || (artistsComplete && !albumsComplete)) {
|
||||
} else if (currentType.includes('album')) {
|
||||
progressText = `Albums: ${albums.matched || 0} / ${albums.total || 0} (${albums.percent || 0}%)`;
|
||||
} else if (currentType.includes('track') || (artistsComplete && albumsComplete)) {
|
||||
} else if (currentType.includes('track')) {
|
||||
progressText = `Tracks: ${tracks.matched || 0} / ${tracks.total || 0} (${tracks.percent || 0}%)`;
|
||||
} else {
|
||||
} else if (!artistsComplete) {
|
||||
progressText = `Artists: ${artists.matched || 0} / ${artists.total || 0} (${artists.percent || 0}%)`;
|
||||
} else if (!albumsComplete) {
|
||||
progressText = `Albums: ${albums.matched || 0} / ${albums.total || 0} (${albums.percent || 0}%)`;
|
||||
} else {
|
||||
progressText = `Tracks: ${tracks.matched || 0} / ${tracks.total || 0} (${tracks.percent || 0}%)`;
|
||||
}
|
||||
|
||||
tooltipProgress.textContent = progressText;
|
||||
|
|
@ -36828,14 +36833,19 @@ async function updateiTunesEnrichmentStatus() {
|
|||
const artistsComplete = artists.matched >= artists.total;
|
||||
const albumsComplete = albums.matched >= albums.total;
|
||||
|
||||
if (currentType === 'artist' || (!artistsComplete && !currentType)) {
|
||||
// Prioritize currentType over completion-based inference
|
||||
if (currentType === 'artist') {
|
||||
progressText = `Artists: ${artists.matched || 0} / ${artists.total || 0} (${artists.percent || 0}%)`;
|
||||
} else if (currentType.includes('album') || (artistsComplete && !albumsComplete)) {
|
||||
} else if (currentType.includes('album')) {
|
||||
progressText = `Albums: ${albums.matched || 0} / ${albums.total || 0} (${albums.percent || 0}%)`;
|
||||
} else if (currentType.includes('track') || (artistsComplete && albumsComplete)) {
|
||||
} else if (currentType.includes('track')) {
|
||||
progressText = `Tracks: ${tracks.matched || 0} / ${tracks.total || 0} (${tracks.percent || 0}%)`;
|
||||
} else {
|
||||
} else if (!artistsComplete) {
|
||||
progressText = `Artists: ${artists.matched || 0} / ${artists.total || 0} (${artists.percent || 0}%)`;
|
||||
} else if (!albumsComplete) {
|
||||
progressText = `Albums: ${albums.matched || 0} / ${albums.total || 0} (${albums.percent || 0}%)`;
|
||||
} else {
|
||||
progressText = `Tracks: ${tracks.matched || 0} / ${tracks.total || 0} (${tracks.percent || 0}%)`;
|
||||
}
|
||||
|
||||
tooltipProgress.textContent = progressText;
|
||||
|
|
|
|||
Loading…
Reference in a new issue