Rename status cache to metadata_source
Expose the primary metadata provider status under a generic cache key and update the websocket fixture plus frontend readers to match.
This commit is contained in:
parent
1d9d399a2f
commit
36267618a3
8 changed files with 73 additions and 73 deletions
|
|
@ -17,7 +17,7 @@ from flask_socketio import SocketIO, join_room, leave_room
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_DEFAULT_STATUS_CACHE = {
|
_DEFAULT_STATUS_CACHE = {
|
||||||
'spotify': {'connected': True, 'authenticated': True, 'response_time': 12.5, 'source': 'spotify'},
|
'metadata_source': {'connected': True, 'authenticated': True, 'response_time': 12.5, 'source': 'spotify'},
|
||||||
'media_server': {'connected': True, 'response_time': 8.1, 'type': 'plex'},
|
'media_server': {'connected': True, 'response_time': 8.1, 'type': 'plex'},
|
||||||
'soulseek': {'connected': True, 'response_time': 5.3, 'source': 'soulseek'},
|
'soulseek': {'connected': True, 'response_time': 5.3, 'source': 'soulseek'},
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +255,7 @@ wishlist_stats_state = copy.deepcopy(_DEFAULT_WISHLIST_STATS)
|
||||||
|
|
||||||
def _build_status_payload():
|
def _build_status_payload():
|
||||||
return {
|
return {
|
||||||
'spotify': dict(_status_cache['spotify']),
|
'metadata_source': dict(_status_cache['metadata_source']),
|
||||||
'media_server': dict(_status_cache['media_server']),
|
'media_server': dict(_status_cache['media_server']),
|
||||||
'soulseek': dict(_status_cache['soulseek']),
|
'soulseek': dict(_status_cache['soulseek']),
|
||||||
'active_media_server': _status_cache['media_server'].get('type', 'plex'),
|
'active_media_server': _status_cache['media_server'].get('type', 'plex'),
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,11 @@ class TestServiceStatus:
|
||||||
status_events = [e for e in received if e['name'] == 'status:update']
|
status_events = [e for e in received if e['name'] == 'status:update']
|
||||||
assert len(status_events) >= 1
|
assert len(status_events) >= 1
|
||||||
data = status_events[0]['args'][0]
|
data = status_events[0]['args'][0]
|
||||||
assert 'spotify' in data
|
assert 'metadata_source' in data
|
||||||
assert 'media_server' in data
|
assert 'media_server' in data
|
||||||
assert 'soulseek' in data
|
assert 'soulseek' in data
|
||||||
assert 'active_media_server' in data
|
assert 'active_media_server' in data
|
||||||
assert 'authenticated' in data['spotify']
|
assert 'authenticated' in data['metadata_source']
|
||||||
|
|
||||||
def test_status_matches_http(self, test_app, shared_state):
|
def test_status_matches_http(self, test_app, shared_state):
|
||||||
"""Socket event data matches HTTP endpoint response exactly."""
|
"""Socket event data matches HTTP endpoint response exactly."""
|
||||||
|
|
@ -73,7 +73,7 @@ class TestServiceStatus:
|
||||||
assert len(status_events) >= 1
|
assert len(status_events) >= 1
|
||||||
ws_data = status_events[0]['args'][0]
|
ws_data = status_events[0]['args'][0]
|
||||||
|
|
||||||
assert ws_data['spotify'] == http_data['spotify']
|
assert ws_data['metadata_source'] == http_data['metadata_source']
|
||||||
assert ws_data['media_server'] == http_data['media_server']
|
assert ws_data['media_server'] == http_data['media_server']
|
||||||
assert ws_data['soulseek'] == http_data['soulseek']
|
assert ws_data['soulseek'] == http_data['soulseek']
|
||||||
assert ws_data['active_media_server'] == http_data['active_media_server']
|
assert ws_data['active_media_server'] == http_data['active_media_server']
|
||||||
|
|
@ -86,13 +86,13 @@ class TestServiceStatus:
|
||||||
build = shared_state['build_status_payload']
|
build = shared_state['build_status_payload']
|
||||||
|
|
||||||
# Mutate cache
|
# Mutate cache
|
||||||
status_cache['spotify']['source'] = 'itunes'
|
status_cache['metadata_source']['source'] = 'itunes'
|
||||||
|
|
||||||
socketio.emit('status:update', build())
|
socketio.emit('status:update', build())
|
||||||
received = client.get_received()
|
received = client.get_received()
|
||||||
status_events = [e for e in received if e['name'] == 'status:update']
|
status_events = [e for e in received if e['name'] == 'status:update']
|
||||||
data = status_events[-1]['args'][0]
|
data = status_events[-1]['args'][0]
|
||||||
assert data['spotify']['source'] == 'itunes'
|
assert data['metadata_source']['source'] == 'itunes'
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -811,12 +811,12 @@ _idle_since = {}
|
||||||
_IDLE_GRACE_SECONDS = 5
|
_IDLE_GRACE_SECONDS = 5
|
||||||
|
|
||||||
_status_cache = {
|
_status_cache = {
|
||||||
'spotify': {'connected': False, 'authenticated': False, 'response_time': 0, 'source': 'itunes'},
|
'metadata_source': {'connected': False, 'authenticated': False, 'response_time': 0, 'source': 'itunes'},
|
||||||
'media_server': {'connected': False, 'response_time': 0, 'type': None},
|
'media_server': {'connected': False, 'response_time': 0, 'type': None},
|
||||||
'soulseek': {'connected': False, 'response_time': 0},
|
'soulseek': {'connected': False, 'response_time': 0},
|
||||||
}
|
}
|
||||||
_status_cache_timestamps: dict[str, float] = {
|
_status_cache_timestamps: dict[str, float] = {
|
||||||
'spotify': 0,
|
'metadata_source': 0,
|
||||||
'media_server': 0,
|
'media_server': 0,
|
||||||
'soulseek': 0,
|
'soulseek': 0,
|
||||||
}
|
}
|
||||||
|
|
@ -3447,12 +3447,12 @@ def get_status():
|
||||||
active_server = config_manager.get_active_media_server()
|
active_server = config_manager.get_active_media_server()
|
||||||
|
|
||||||
# Test Spotify - with caching to avoid excessive API calls
|
# Test Spotify - with caching to avoid excessive API calls
|
||||||
if current_time - _status_cache_timestamps['spotify'] > STATUS_CACHE_TTL:
|
if current_time - _status_cache_timestamps['metadata_source'] > STATUS_CACHE_TTL:
|
||||||
# Guard against spotify_client being None (partial init)
|
# Guard against spotify_client being None (partial init)
|
||||||
is_rate_limited = spotify_client.is_rate_limited() if spotify_client else False
|
is_rate_limited = spotify_client.is_rate_limited() if spotify_client else False
|
||||||
rate_limit_info = spotify_client.get_rate_limit_info() if (spotify_client and is_rate_limited) else None
|
rate_limit_info = spotify_client.get_rate_limit_info() if (spotify_client and is_rate_limited) else None
|
||||||
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
|
||||||
spotify_session_active = spotify_client.is_spotify_authenticated() if spotify_client else False
|
metadata_source_authenticated = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||||
|
|
||||||
# Read configured source once — no auth validation here, we do that explicitly below
|
# Read configured source once — no auth validation here, we do that explicitly below
|
||||||
configured_source = config_manager.get('metadata.fallback_source', 'deezer') or 'deezer'
|
configured_source = config_manager.get('metadata.fallback_source', 'deezer') or 'deezer'
|
||||||
|
|
@ -3461,27 +3461,27 @@ def get_status():
|
||||||
# During rate limit or post-ban cooldown, skip the auth probe entirely.
|
# During rate limit or post-ban cooldown, skip the auth probe entirely.
|
||||||
# Probing Spotify here would reset the rate limit timer.
|
# Probing Spotify here would reset the rate limit timer.
|
||||||
music_source = 'deezer' if configured_source == 'spotify' else configured_source
|
music_source = 'deezer' if configured_source == 'spotify' else configured_source
|
||||||
spotify_response_time = 0
|
metadata_source_response_time = 0
|
||||||
else:
|
else:
|
||||||
spotify_start = time.time()
|
metadata_source_start = time.time()
|
||||||
spotify_connected = spotify_client.is_spotify_authenticated() if spotify_client else False
|
metadata_source_connected = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||||
spotify_response_time = (time.time() - spotify_start) * 1000
|
metadata_source_response_time = (time.time() - metadata_source_start) * 1000
|
||||||
# Use configured source; fall back to deezer only if Spotify isn't authenticated
|
# Use configured source; fall back to deezer only if Spotify isn't authenticated
|
||||||
if configured_source == 'spotify':
|
if configured_source == 'spotify':
|
||||||
music_source = 'spotify' if spotify_connected else 'deezer'
|
music_source = 'spotify' if metadata_source_connected else 'deezer'
|
||||||
else:
|
else:
|
||||||
music_source = configured_source
|
music_source = configured_source
|
||||||
|
|
||||||
_status_cache['spotify'] = {
|
_status_cache['metadata_source'] = {
|
||||||
'connected': spotify_session_active,
|
'connected': metadata_source_authenticated,
|
||||||
'authenticated': spotify_session_active,
|
'authenticated': metadata_source_authenticated,
|
||||||
'response_time': round(spotify_response_time, 1),
|
'response_time': round(metadata_source_response_time, 1),
|
||||||
'source': music_source,
|
'source': music_source,
|
||||||
'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
|
||||||
}
|
}
|
||||||
_status_cache_timestamps['spotify'] = current_time
|
_status_cache_timestamps['metadata_source'] = current_time
|
||||||
# else: use cached value
|
# else: use cached value
|
||||||
|
|
||||||
# Test media server - use EXISTING instances (they have internal caching)
|
# Test media server - use EXISTING instances (they have internal caching)
|
||||||
|
|
@ -3556,7 +3556,7 @@ def get_status():
|
||||||
active_dl_count += 1
|
active_dl_count += 1
|
||||||
|
|
||||||
status_data = {
|
status_data = {
|
||||||
'spotify': _status_cache['spotify'],
|
'metadata_source': _status_cache['metadata_source'],
|
||||||
'media_server': _status_cache['media_server'],
|
'media_server': _status_cache['media_server'],
|
||||||
'soulseek': _status_cache['soulseek'],
|
'soulseek': _status_cache['soulseek'],
|
||||||
'active_media_server': active_server,
|
'active_media_server': active_server,
|
||||||
|
|
@ -4186,7 +4186,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
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:
|
||||||
|
|
@ -4804,10 +4804,10 @@ def test_connection_endpoint():
|
||||||
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
|
spotify_session_active = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||||
_status_cache['spotify']['connected'] = True
|
_status_cache['metadata_source']['connected'] = True
|
||||||
_status_cache['spotify']['authenticated'] = spotify_session_active
|
_status_cache['metadata_source']['authenticated'] = spotify_session_active
|
||||||
_status_cache['spotify']['source'] = _get_metadata_fallback_source()
|
_status_cache['metadata_source']['source'] = _get_metadata_fallback_source()
|
||||||
_status_cache_timestamps['spotify'] = current_time
|
_status_cache_timestamps['metadata_source'] = current_time
|
||||||
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
|
||||||
|
|
@ -4972,10 +4972,10 @@ def test_dashboard_connection_endpoint():
|
||||||
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
|
spotify_session_active = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||||
_status_cache['spotify']['connected'] = True
|
_status_cache['metadata_source']['connected'] = True
|
||||||
_status_cache['spotify']['authenticated'] = spotify_session_active
|
_status_cache['metadata_source']['authenticated'] = spotify_session_active
|
||||||
_status_cache['spotify']['source'] = _get_metadata_fallback_source()
|
_status_cache['metadata_source']['source'] = _get_metadata_fallback_source()
|
||||||
_status_cache_timestamps['spotify'] = current_time
|
_status_cache_timestamps['metadata_source'] = current_time
|
||||||
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
|
||||||
|
|
@ -5854,7 +5854,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
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.",
|
||||||
|
|
@ -5890,7 +5890,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
# 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()
|
||||||
|
|
@ -5900,7 +5900,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
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.",
|
||||||
|
|
@ -5929,7 +5929,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['spotify'] = {
|
_status_cache['metadata_source'] = {
|
||||||
'connected': False,
|
'connected': False,
|
||||||
'authenticated': False,
|
'authenticated': False,
|
||||||
'response_time': 0,
|
'response_time': 0,
|
||||||
|
|
@ -5938,7 +5938,7 @@ def spotify_disconnect():
|
||||||
'rate_limit': None,
|
'rate_limit': None,
|
||||||
'post_ban_cooldown': None
|
'post_ban_cooldown': None
|
||||||
}
|
}
|
||||||
_status_cache_timestamps['spotify'] = time.time()
|
_status_cache_timestamps['metadata_source'] = time.time()
|
||||||
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,
|
||||||
|
|
@ -32073,7 +32073,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
# 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()
|
||||||
|
|
@ -32086,7 +32086,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['spotify'] = 0
|
_status_cache_timestamps['metadata_source'] = 0
|
||||||
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')
|
||||||
|
|
@ -34053,14 +34053,14 @@ def _build_status_payload():
|
||||||
|
|
||||||
# Always include fresh rate limit info (it changes over time as ban expires)
|
# Always include fresh rate limit info (it changes over time as ban expires)
|
||||||
# Call is_rate_limited() first to ensure ban-end timestamp is recorded for cooldown
|
# Call is_rate_limited() first to ensure ban-end timestamp is recorded for cooldown
|
||||||
spotify_data = dict(_status_cache.get('spotify', {}))
|
metadata_source_data = dict(_status_cache.get('metadata_source', {}))
|
||||||
is_rl = spotify_client.is_rate_limited() if spotify_client else False
|
is_rl = spotify_client.is_rate_limited() if spotify_client else False
|
||||||
rate_limit_info = spotify_client.get_rate_limit_info() if is_rl else None
|
rate_limit_info = spotify_client.get_rate_limit_info() if is_rl else None
|
||||||
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
|
||||||
spotify_data['rate_limited'] = is_rl
|
metadata_source_data['rate_limited'] = is_rl
|
||||||
spotify_data['rate_limit'] = rate_limit_info
|
metadata_source_data['rate_limit'] = rate_limit_info
|
||||||
if cooldown_remaining > 0:
|
if cooldown_remaining > 0:
|
||||||
spotify_data['post_ban_cooldown'] = cooldown_remaining
|
metadata_source_data['post_ban_cooldown'] = cooldown_remaining
|
||||||
|
|
||||||
# Count active downloads for nav badge
|
# Count active downloads for nav badge
|
||||||
active_dl_count = 0
|
active_dl_count = 0
|
||||||
|
|
@ -34073,7 +34073,7 @@ def _build_status_payload():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'spotify': spotify_data,
|
'metadata_source': metadata_source_data,
|
||||||
'media_server': _status_cache.get('media_server', {}),
|
'media_server': _status_cache.get('media_server', {}),
|
||||||
'soulseek': soulseek_data,
|
'soulseek': soulseek_data,
|
||||||
'active_media_server': config_manager.get_active_media_server(),
|
'active_media_server': config_manager.get_active_media_server(),
|
||||||
|
|
|
||||||
|
|
@ -471,24 +471,24 @@ function initializeWebSocket() {
|
||||||
|
|
||||||
function handleServiceStatusUpdate(data) {
|
function handleServiceStatusUpdate(data) {
|
||||||
// Cache for library status card
|
// Cache for library status card
|
||||||
_lastServiceStatus = data;
|
_lastStatusPayload = data;
|
||||||
|
|
||||||
if (typeof syncSpotifySettingsAuthState === 'function') {
|
if (typeof syncSpotifySettingsAuthState === 'function') {
|
||||||
syncSpotifySettingsAuthState(data?.spotify || null);
|
syncSpotifySettingsAuthState(data?.metadata_source || null);
|
||||||
}
|
}
|
||||||
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
||||||
syncPrimaryMetadataSourceAvailability(data?.spotify || null);
|
syncPrimaryMetadataSourceAvailability(data?.metadata_source || null);
|
||||||
}
|
}
|
||||||
if (typeof sanitizeMetadataSourceSelection === 'function') {
|
if (typeof sanitizeMetadataSourceSelection === 'function') {
|
||||||
sanitizeMetadataSourceSelection({ quiet: true });
|
sanitizeMetadataSourceSelection({ quiet: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same logic as fetchAndUpdateServiceStatus response handler
|
// Same logic as fetchAndUpdateServiceStatus response handler
|
||||||
updateServiceStatus('spotify', data.spotify);
|
updateServiceStatus('spotify', data.metadata_source);
|
||||||
updateServiceStatus('media-server', data.media_server);
|
updateServiceStatus('media-server', data.media_server);
|
||||||
updateServiceStatus('soulseek', data.soulseek);
|
updateServiceStatus('soulseek', data.soulseek);
|
||||||
|
|
||||||
updateSidebarServiceStatus('spotify', data.spotify);
|
updateSidebarServiceStatus('spotify', data.metadata_source);
|
||||||
updateSidebarServiceStatus('media-server', data.media_server);
|
updateSidebarServiceStatus('media-server', data.media_server);
|
||||||
updateSidebarServiceStatus('soulseek', data.soulseek);
|
updateSidebarServiceStatus('soulseek', data.soulseek);
|
||||||
|
|
||||||
|
|
@ -514,10 +514,10 @@ function handleServiceStatusUpdate(data) {
|
||||||
if (data.enrichment) renderEnrichmentCards(data.enrichment);
|
if (data.enrichment) renderEnrichmentCards(data.enrichment);
|
||||||
|
|
||||||
// Spotify rate limit / cooldown / recovery
|
// Spotify rate limit / cooldown / recovery
|
||||||
if (data.spotify?.rate_limited && data.spotify.rate_limit) {
|
if (data.metadata_source?.rate_limited && data.metadata_source.rate_limit) {
|
||||||
handleSpotifyRateLimit(data.spotify.rate_limit);
|
handleSpotifyRateLimit(data.metadata_source.rate_limit);
|
||||||
_spotifyInCooldown = false;
|
_spotifyInCooldown = false;
|
||||||
} else if (data.spotify?.post_ban_cooldown > 0) {
|
} else if (data.metadata_source?.post_ban_cooldown > 0) {
|
||||||
if (_spotifyRateLimitShown && !_spotifyInCooldown) {
|
if (_spotifyRateLimitShown && !_spotifyInCooldown) {
|
||||||
_spotifyRateLimitShown = false;
|
_spotifyRateLimitShown = false;
|
||||||
_spotifyInCooldown = true;
|
_spotifyInCooldown = true;
|
||||||
|
|
@ -881,12 +881,12 @@ const API = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track last service status for library card (used by websocket handler in core + artists)
|
// Track the last `/status` payload (shared service snapshot used across the UI)
|
||||||
let _lastServiceStatus = null;
|
let _lastStatusPayload = null;
|
||||||
let _isSoulsyncStandalone = false; // Global flag: true when no media server (sync buttons hidden)
|
let _isSoulsyncStandalone = false; // Global flag: true when no media server (sync buttons hidden)
|
||||||
|
|
||||||
function getActiveMetadataSource() {
|
function getActiveMetadataSource() {
|
||||||
return _lastServiceStatus?.spotify?.source || 'spotify';
|
return _lastStatusPayload?.metadata_source?.source || 'spotify';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
|
||||||
|
|
@ -2974,13 +2974,13 @@ async function _checkSetupStatus() {
|
||||||
const completion = _getSetupCompletion();
|
const completion = _getSetupCompletion();
|
||||||
const results = { ...completion };
|
const results = { ...completion };
|
||||||
|
|
||||||
// ── /status — checks services (spotify, media_server, soulseek) ─────
|
// ── /status — checks metadata_source, media_server, soulseek ────────
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/status');
|
const resp = await fetch('/status');
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
// Metadata source is available when status reports a source.
|
// Metadata source is available when status reports a source.
|
||||||
if (data.spotify?.source) {
|
if (data.metadata_source?.source) {
|
||||||
results['metadata-source'] = results['metadata-source'] || Date.now();
|
results['metadata-source'] = results['metadata-source'] || Date.now();
|
||||||
_markSetupComplete('metadata-source');
|
_markSetupComplete('metadata-source');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ function syncMetadataSourceSelection(source) {
|
||||||
|
|
||||||
function _isMetadataSourceSelectable(source) {
|
function _isMetadataSourceSelectable(source) {
|
||||||
if (source === 'spotify') {
|
if (source === 'spotify') {
|
||||||
return _lastServiceStatus?.spotify?.authenticated === true;
|
return _lastStatusPayload?.metadata_source?.authenticated === true;
|
||||||
}
|
}
|
||||||
if (source === 'discogs') {
|
if (source === 'discogs') {
|
||||||
const token = document.getElementById('discogs-token');
|
const token = document.getElementById('discogs-token');
|
||||||
|
|
@ -173,7 +173,7 @@ function initializeSettings() {
|
||||||
if (discogsTokenInput) {
|
if (discogsTokenInput) {
|
||||||
discogsTokenInput.addEventListener('input', () => {
|
discogsTokenInput.addEventListener('input', () => {
|
||||||
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
||||||
syncPrimaryMetadataSourceAvailability(_lastServiceStatus?.spotify || null);
|
syncPrimaryMetadataSourceAvailability(_lastStatusPayload?.metadata_source || null);
|
||||||
}
|
}
|
||||||
sanitizeMetadataSourceSelection({ quiet: true });
|
sanitizeMetadataSourceSelection({ quiet: true });
|
||||||
});
|
});
|
||||||
|
|
@ -207,10 +207,10 @@ function initializeSettings() {
|
||||||
// Test button event listeners removed - they use onclick attributes in HTML to avoid double firing
|
// Test button event listeners removed - they use onclick attributes in HTML to avoid double firing
|
||||||
|
|
||||||
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
||||||
syncPrimaryMetadataSourceAvailability(_lastServiceStatus?.spotify || null);
|
syncPrimaryMetadataSourceAvailability(_lastStatusPayload?.metadata_source || null);
|
||||||
}
|
}
|
||||||
syncSpotifySettingsAuthState(_lastServiceStatus?.spotify || null);
|
syncSpotifySettingsAuthState(_lastStatusPayload?.metadata_source || null);
|
||||||
syncMetadataSourceSelection(_lastServiceStatus?.spotify?.source);
|
syncMetadataSourceSelection(_lastStatusPayload?.metadata_source?.source);
|
||||||
sanitizeMetadataSourceSelection({ quiet: true });
|
sanitizeMetadataSourceSelection({ quiet: true });
|
||||||
if (metadataSourceSelect) {
|
if (metadataSourceSelect) {
|
||||||
metadataSourceSelect.dataset.lastValidSource = metadataSourceSelect.value;
|
metadataSourceSelect.dataset.lastValidSource = metadataSourceSelect.value;
|
||||||
|
|
@ -408,7 +408,7 @@ async function applyServiceStatusGradients() {
|
||||||
else header.appendChild(spinner);
|
else header.appendChild(spinner);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
syncSpotifySettingsAuthState(_lastServiceStatus?.spotify || null);
|
syncSpotifySettingsAuthState(_lastStatusPayload?.metadata_source || null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[Settings Status] Failed to apply gradients:', e);
|
console.warn('[Settings Status] Failed to apply gradients:', e);
|
||||||
}
|
}
|
||||||
|
|
@ -2544,7 +2544,7 @@ async function saveSettings(quiet = false) {
|
||||||
const discogsTokenInput = document.getElementById('discogs-token');
|
const discogsTokenInput = document.getElementById('discogs-token');
|
||||||
const discogsTokenPresent = !!discogsTokenInput?.value?.trim();
|
const discogsTokenPresent = !!discogsTokenInput?.value?.trim();
|
||||||
let metadataSource = metadataSourceSelect?.value || 'itunes';
|
let metadataSource = metadataSourceSelect?.value || 'itunes';
|
||||||
const spotifySessionActive = _lastServiceStatus?.spotify?.authenticated === true;
|
const spotifySessionActive = _lastStatusPayload?.metadata_source?.authenticated === true;
|
||||||
if (metadataSource === 'spotify' && !spotifySessionActive) {
|
if (metadataSource === 'spotify' && !spotifySessionActive) {
|
||||||
metadataSource = 'deezer';
|
metadataSource = 'deezer';
|
||||||
if (metadataSourceSelect) metadataSourceSelect.value = metadataSource;
|
if (metadataSourceSelect) metadataSourceSelect.value = metadataSource;
|
||||||
|
|
|
||||||
|
|
@ -3143,25 +3143,25 @@ async function fetchAndUpdateServiceStatus() {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Cache for library status card
|
// Cache for library status card
|
||||||
_lastServiceStatus = data;
|
_lastStatusPayload = data;
|
||||||
|
|
||||||
if (typeof syncSpotifySettingsAuthState === 'function') {
|
if (typeof syncSpotifySettingsAuthState === 'function') {
|
||||||
syncSpotifySettingsAuthState(data?.spotify || null);
|
syncSpotifySettingsAuthState(data?.metadata_source || null);
|
||||||
}
|
}
|
||||||
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
|
||||||
syncPrimaryMetadataSourceAvailability(data?.spotify || null);
|
syncPrimaryMetadataSourceAvailability(data?.metadata_source || null);
|
||||||
}
|
}
|
||||||
if (typeof sanitizeMetadataSourceSelection === 'function') {
|
if (typeof sanitizeMetadataSourceSelection === 'function') {
|
||||||
sanitizeMetadataSourceSelection({ quiet: true });
|
sanitizeMetadataSourceSelection({ quiet: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update service status indicators and text (dashboard)
|
// Update service status indicators and text (dashboard)
|
||||||
updateServiceStatus('spotify', data.spotify);
|
updateServiceStatus('spotify', data.metadata_source);
|
||||||
updateServiceStatus('media-server', data.media_server);
|
updateServiceStatus('media-server', data.media_server);
|
||||||
updateServiceStatus('soulseek', data.soulseek);
|
updateServiceStatus('soulseek', data.soulseek);
|
||||||
|
|
||||||
// Update sidebar service status indicators
|
// Update sidebar service status indicators
|
||||||
updateSidebarServiceStatus('spotify', data.spotify);
|
updateSidebarServiceStatus('spotify', data.metadata_source);
|
||||||
updateSidebarServiceStatus('media-server', data.media_server);
|
updateSidebarServiceStatus('media-server', data.media_server);
|
||||||
updateSidebarServiceStatus('soulseek', data.soulseek);
|
updateSidebarServiceStatus('soulseek', data.soulseek);
|
||||||
|
|
||||||
|
|
@ -3185,8 +3185,8 @@ async function fetchAndUpdateServiceStatus() {
|
||||||
if (data.enrichment) renderEnrichmentCards(data.enrichment);
|
if (data.enrichment) renderEnrichmentCards(data.enrichment);
|
||||||
|
|
||||||
// Check for Spotify rate limit
|
// Check for Spotify rate limit
|
||||||
if (data.spotify && data.spotify.rate_limited && data.spotify.rate_limit) {
|
if (data.metadata_source && data.metadata_source.rate_limited && data.metadata_source.rate_limit) {
|
||||||
handleSpotifyRateLimit(data.spotify.rate_limit);
|
handleSpotifyRateLimit(data.metadata_source.rate_limit);
|
||||||
} else if (_spotifyRateLimitShown) {
|
} else if (_spotifyRateLimitShown) {
|
||||||
handleSpotifyRateLimit(null);
|
handleSpotifyRateLimit(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6558,8 +6558,8 @@ function updateLibraryStatusCard(dbStats) {
|
||||||
const isScanning = window._libraryStatusScanning || false;
|
const isScanning = window._libraryStatusScanning || false;
|
||||||
|
|
||||||
// Determine state
|
// Determine state
|
||||||
const serverConnected = _lastServiceStatus && _lastServiceStatus.media_server && _lastServiceStatus.media_server.connected;
|
const serverConnected = _lastStatusPayload && _lastStatusPayload.media_server && _lastStatusPayload.media_server.connected;
|
||||||
const serverType = _lastServiceStatus && _lastServiceStatus.active_media_server;
|
const serverType = _lastStatusPayload && _lastStatusPayload.active_media_server;
|
||||||
const hasData = tracks > 0;
|
const hasData = tracks > 0;
|
||||||
const hasServer = !!serverType && serverType !== 'none';
|
const hasServer = !!serverType && serverType !== 'none';
|
||||||
|
|
||||||
|
|
@ -6668,7 +6668,7 @@ function updateLibraryStatusCard(dbStats) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _lastServiceStatus and _isSoulsyncStandalone are declared in core.js
|
// _lastStatusPayload and _isSoulsyncStandalone are declared in core.js
|
||||||
const _origFetchServiceStatus = typeof fetchAndUpdateServiceStatus === 'function' ? fetchAndUpdateServiceStatus : null;
|
const _origFetchServiceStatus = typeof fetchAndUpdateServiceStatus === 'function' ? fetchAndUpdateServiceStatus : null;
|
||||||
|
|
||||||
function _capitalize(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : ''; }
|
function _capitalize(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : ''; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue