Reduce Spotify status polling
- cache Spotify auth and rate-limit status separately from the generic metadata source snapshot - refresh Spotify status only on explicit auth/disconnect/test paths or after the TTL expires - keep the legacy OAuth callback paths aligned with the same invalidation helper
This commit is contained in:
parent
e2bd0e1871
commit
3c7187fb32
1 changed files with 39 additions and 14 deletions
|
|
@ -821,6 +821,24 @@ _status_cache_timestamps: dict[str, float] = {
|
||||||
'soulseek': 0,
|
'soulseek': 0,
|
||||||
}
|
}
|
||||||
STATUS_CACHE_TTL = 120
|
STATUS_CACHE_TTL = 120
|
||||||
|
SPOTIFY_STATUS_TTL_ACTIVE = 15
|
||||||
|
SPOTIFY_STATUS_TTL_IDLE = 300
|
||||||
|
|
||||||
|
_spotify_status_cache = {
|
||||||
|
'connected': False,
|
||||||
|
'authenticated': False,
|
||||||
|
'rate_limited': False,
|
||||||
|
'rate_limit': None,
|
||||||
|
'post_ban_cooldown': None,
|
||||||
|
}
|
||||||
|
_spotify_status_timestamp = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _invalidate_metadata_status_caches():
|
||||||
|
"""Mark the metadata-source and Spotify status snapshots stale."""
|
||||||
|
global _spotify_status_timestamp
|
||||||
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
|
_spotify_status_timestamp = 0
|
||||||
|
|
||||||
dev_mode_enabled = False
|
dev_mode_enabled = False
|
||||||
_hydrabase_ws = None
|
_hydrabase_ws = None
|
||||||
|
|
@ -4155,7 +4173,7 @@ def handle_settings():
|
||||||
if tidal_enrichment_worker:
|
if tidal_enrichment_worker:
|
||||||
tidal_enrichment_worker.client = tidal_client
|
tidal_enrichment_worker.client = tidal_client
|
||||||
# Invalidate status cache so next poll reflects new settings (e.g. fallback source change)
|
# Invalidate status cache so next poll reflects new settings (e.g. fallback source change)
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
logger.info("Service clients re-initialized with new settings.")
|
logger.info("Service clients re-initialized with new settings.")
|
||||||
return jsonify({"success": True, "message": "Settings saved successfully."})
|
return jsonify({"success": True, "message": "Settings saved successfully."})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -4772,8 +4790,7 @@ def test_connection_endpoint():
|
||||||
if success:
|
if success:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if service == 'spotify':
|
if service == 'spotify':
|
||||||
spotify_session_active = spotify_client.is_spotify_authenticated() if spotify_client else False
|
_invalidate_metadata_status_caches()
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
|
||||||
logger.info("Updated Spotify status cache after successful test")
|
logger.info("Updated Spotify status cache after successful test")
|
||||||
elif service in ['plex', 'jellyfin', 'navidrome', 'soulsync']:
|
elif service in ['plex', 'jellyfin', 'navidrome', 'soulsync']:
|
||||||
_status_cache['media_server']['connected'] = True
|
_status_cache['media_server']['connected'] = True
|
||||||
|
|
@ -4937,8 +4954,7 @@ def test_dashboard_connection_endpoint():
|
||||||
if success:
|
if success:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if service == 'spotify':
|
if service == 'spotify':
|
||||||
spotify_session_active = spotify_client.is_spotify_authenticated() if spotify_client else False
|
_invalidate_metadata_status_caches()
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
|
||||||
logger.info("Updated Spotify status cache after successful dashboard test")
|
logger.info("Updated Spotify status cache after successful dashboard test")
|
||||||
elif service in ['plex', 'jellyfin', 'navidrome', 'soulsync']:
|
elif service in ['plex', 'jellyfin', 'navidrome', 'soulsync']:
|
||||||
_status_cache['media_server']['connected'] = True
|
_status_cache['media_server']['connected'] = True
|
||||||
|
|
@ -5817,7 +5833,7 @@ def spotify_callback():
|
||||||
return _spotify_auth_result_page("Your personal Spotify account is now connected. You can close this window.", authenticated=True)
|
return _spotify_auth_result_page("Your personal Spotify account is now connected. You can close this window.", authenticated=True)
|
||||||
if profile_client:
|
if profile_client:
|
||||||
profile_client._invalidate_auth_cache()
|
profile_client._invalidate_auth_cache()
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
add_activity_item("", "Spotify Auth Warning", f"Profile {profile_id_from_state} completed OAuth but Spotify did not confirm an authenticated session", "Now")
|
add_activity_item("", "Spotify Auth Warning", f"Profile {profile_id_from_state} completed OAuth but Spotify did not confirm an authenticated session", "Now")
|
||||||
return _spotify_auth_result_page(
|
return _spotify_auth_result_page(
|
||||||
"Spotify authorization completed, but SoulSync could not confirm an authenticated Spotify session for this profile. You can close this window and try Authenticate again.",
|
"Spotify authorization completed, but SoulSync could not confirm an authenticated Spotify session for this profile. You can close this window and try Authenticate again.",
|
||||||
|
|
@ -5853,7 +5869,7 @@ def spotify_callback():
|
||||||
_clear_rate_limit()
|
_clear_rate_limit()
|
||||||
spotify_client._invalidate_auth_cache()
|
spotify_client._invalidate_auth_cache()
|
||||||
# Invalidate status cache so next poll picks up the new connection
|
# Invalidate status cache so next poll picks up the new connection
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
# Refresh enrichment worker's client so it picks up new auth
|
# Refresh enrichment worker's client so it picks up new auth
|
||||||
if spotify_enrichment_worker and hasattr(spotify_enrichment_worker, 'client'):
|
if spotify_enrichment_worker and hasattr(spotify_enrichment_worker, 'client'):
|
||||||
spotify_enrichment_worker.client.reload_config()
|
spotify_enrichment_worker.client.reload_config()
|
||||||
|
|
@ -5863,7 +5879,7 @@ def spotify_callback():
|
||||||
else:
|
else:
|
||||||
logger.warning("Spotify OAuth token exchange succeeded but authentication validation failed")
|
logger.warning("Spotify OAuth token exchange succeeded but authentication validation failed")
|
||||||
spotify_client._invalidate_auth_cache()
|
spotify_client._invalidate_auth_cache()
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
add_activity_item("", "Spotify Auth Warning", "OAuth completed, but Spotify did not confirm an authenticated session", "Now")
|
add_activity_item("", "Spotify Auth Warning", "OAuth completed, but Spotify did not confirm an authenticated session", "Now")
|
||||||
return _spotify_auth_result_page(
|
return _spotify_auth_result_page(
|
||||||
"Spotify authorization completed, but SoulSync could not confirm an authenticated Spotify session. You can close this window and try Authenticate again.",
|
"Spotify authorization completed, but SoulSync could not confirm an authenticated Spotify session. You can close this window and try Authenticate again.",
|
||||||
|
|
@ -5892,7 +5908,7 @@ def spotify_disconnect():
|
||||||
source_label = get_metadata_source_label(active_source)
|
source_label = get_metadata_source_label(active_source)
|
||||||
if configured_source == 'spotify':
|
if configured_source == 'spotify':
|
||||||
config_manager.set('metadata.fallback_source', active_source)
|
config_manager.set('metadata.fallback_source', active_source)
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
add_activity_item("", "Spotify Disconnected", f"Using {source_label} for metadata", "Now")
|
add_activity_item("", "Spotify Disconnected", f"Using {source_label} for metadata", "Now")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|
@ -32027,7 +32043,7 @@ def start_oauth_callback_servers():
|
||||||
_clear_rate_limit()
|
_clear_rate_limit()
|
||||||
spotify_client._invalidate_auth_cache()
|
spotify_client._invalidate_auth_cache()
|
||||||
# Invalidate status cache so next poll picks up the new connection
|
# Invalidate status cache so next poll picks up the new connection
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
# Refresh enrichment worker's client so it picks up new auth
|
# Refresh enrichment worker's client so it picks up new auth
|
||||||
if spotify_enrichment_worker and hasattr(spotify_enrichment_worker, 'client'):
|
if spotify_enrichment_worker and hasattr(spotify_enrichment_worker, 'client'):
|
||||||
spotify_enrichment_worker.client.reload_config()
|
spotify_enrichment_worker.client.reload_config()
|
||||||
|
|
@ -32040,7 +32056,7 @@ def start_oauth_callback_servers():
|
||||||
else:
|
else:
|
||||||
_oauth_logger.warning("Spotify token exchange succeeded but authentication validation failed")
|
_oauth_logger.warning("Spotify token exchange succeeded but authentication validation failed")
|
||||||
spotify_client._invalidate_auth_cache()
|
spotify_client._invalidate_auth_cache()
|
||||||
_status_cache_timestamps['metadata_source'] = 0
|
_invalidate_metadata_status_caches()
|
||||||
add_activity_item("", "Spotify Auth Warning", "OAuth completed, but Spotify did not confirm an authenticated session", "Now")
|
add_activity_item("", "Spotify Auth Warning", "OAuth completed, but Spotify did not confirm an authenticated session", "Now")
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
|
|
@ -34029,7 +34045,15 @@ def _build_status_payload():
|
||||||
|
|
||||||
def _build_spotify_status_payload():
|
def _build_spotify_status_payload():
|
||||||
"""Build a Spotify-specific status snapshot for auth and rate-limit state."""
|
"""Build a Spotify-specific status snapshot for auth and rate-limit state."""
|
||||||
status_data = {}
|
import time
|
||||||
|
|
||||||
|
global _spotify_status_timestamp
|
||||||
|
current_time = time.time()
|
||||||
|
configured_source = config_manager.get('metadata.fallback_source', 'deezer') or 'deezer'
|
||||||
|
ttl = SPOTIFY_STATUS_TTL_ACTIVE if configured_source == 'spotify' else SPOTIFY_STATUS_TTL_IDLE
|
||||||
|
|
||||||
|
if _spotify_status_timestamp and current_time - _spotify_status_timestamp <= ttl:
|
||||||
|
return dict(_spotify_status_cache)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Always include fresh rate limit info because it can change independently of cache TTL.
|
# Always include fresh rate limit info because it can change independently of cache TTL.
|
||||||
|
|
@ -34038,17 +34062,18 @@ def _build_spotify_status_payload():
|
||||||
cooldown_remaining = spotify_client.get_post_ban_cooldown_remaining() if spotify_client else 0
|
cooldown_remaining = spotify_client.get_post_ban_cooldown_remaining() if spotify_client else 0
|
||||||
authenticated = spotify_client.is_spotify_authenticated() if spotify_client else False
|
authenticated = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||||
|
|
||||||
status_data.update({
|
_spotify_status_cache.update({
|
||||||
'connected': authenticated,
|
'connected': authenticated,
|
||||||
'authenticated': authenticated,
|
'authenticated': authenticated,
|
||||||
'rate_limited': is_rate_limited,
|
'rate_limited': is_rate_limited,
|
||||||
'rate_limit': rate_limit_info,
|
'rate_limit': rate_limit_info,
|
||||||
'post_ban_cooldown': cooldown_remaining if cooldown_remaining > 0 else None,
|
'post_ban_cooldown': cooldown_remaining if cooldown_remaining > 0 else None,
|
||||||
})
|
})
|
||||||
|
_spotify_status_timestamp = current_time
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return status_data
|
return dict(_spotify_status_cache)
|
||||||
|
|
||||||
def _build_watchlist_count_payload(profile_id=1):
|
def _build_watchlist_count_payload(profile_id=1):
|
||||||
"""Build the same payload used by GET /api/watchlist/count."""
|
"""Build the same payload used by GET /api/watchlist/count."""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue