diff --git a/core/metadata/registry.py b/core/metadata/registry.py index 214aa3f6..71206cfe 100644 --- a/core/metadata/registry.py +++ b/core/metadata/registry.py @@ -309,7 +309,7 @@ def get_spotify_disconnect_source(configured_source: Optional[str] = None) -> st def get_metadata_source_label(source: str) -> str: """Return a human-readable label for a metadata source.""" - return METADATA_SOURCE_LABELS.get(source, source.replace("_", " ").title()) + return METADATA_SOURCE_LABELS.get(source, "Unmapped") def get_source_priority(preferred_source: str): diff --git a/tests/metadata/test_metadata_registry.py b/tests/metadata/test_metadata_registry.py index e4701d99..cc83af52 100644 --- a/tests/metadata/test_metadata_registry.py +++ b/tests/metadata/test_metadata_registry.py @@ -22,5 +22,5 @@ def test_metadata_source_label_maps_known_sources(): assert registry.get_metadata_source_label("hydrabase") == "Hydrabase" -def test_metadata_source_label_falls_back_to_title_case(): - assert registry.get_metadata_source_label("apple_music") == "Apple Music" +def test_metadata_source_label_falls_back_to_unmapped(): + assert registry.get_metadata_source_label("apple_music") == "Unmapped" diff --git a/webui/static/settings.js b/webui/static/settings.js index bb4e1e9d..dce37adc 100644 --- a/webui/static/settings.js +++ b/webui/static/settings.js @@ -2414,8 +2414,8 @@ async function saveSettings(quiet = false) { const metadataSourceSelect = document.getElementById('metadata-fallback-source'); let metadataSource = metadataSourceSelect?.value || 'itunes'; - const spotifyDisconnected = _lastServiceStatus?.spotify?.connected === false; - if (metadataSource === 'spotify' && spotifyDisconnected) { + const spotifySessionActive = _lastServiceStatus?.spotify?.authenticated === true; + if (metadataSource === 'spotify' && !spotifySessionActive) { metadataSource = 'deezer'; if (metadataSourceSelect) metadataSourceSelect.value = metadataSource; if (!quiet) { diff --git a/webui/static/shared-helpers.js b/webui/static/shared-helpers.js index 299f90ac..928588fe 100644 --- a/webui/static/shared-helpers.js +++ b/webui/static/shared-helpers.js @@ -3186,33 +3186,86 @@ function syncSpotifyMetadataSourceAvailability(statusData) { const spotifyOption = select.querySelector('option[value="spotify"]'); if (!spotifyOption) return; - const spotifyAvailable = statusData?.connected === true; + const spotifyAvailable = statusData?.authenticated === true; spotifyOption.disabled = !spotifyAvailable; spotifyOption.dataset.unavailable = spotifyAvailable ? 'false' : 'true'; } +function getMetadataSourceLabel(source) { + if (source === 'deezer') return 'Deezer'; + if (source === 'discogs') return 'Discogs'; + if (source === 'itunes') return 'iTunes'; + if (source === 'spotify') return 'Spotify'; + return 'Unmapped'; +} + +function getSpotifyStatusPresentation(statusData) { + const sourceLabel = getMetadataSourceLabel(statusData?.source); + const rateLimited = !!(statusData?.rate_limited && statusData?.rate_limit); + const cooldown = !!(statusData?.post_ban_cooldown > 0); + const sessionActive = statusData?.authenticated === true || (statusData?.authenticated === undefined && statusData?.source === 'spotify'); + + if (rateLimited) { + const remaining = statusData.rate_limit?.remaining_seconds || 0; + return { + statusClass: 'rate-limited', + statusText: `Spotify paused \u2014 ${formatRateLimitDuration(remaining)}`, + dotClass: 'rate-limited', + dotTitle: `Spotify paused \u2014 ${formatRateLimitDuration(remaining)} remaining`, + sessionActive + }; + } + + if (cooldown) { + const remaining = statusData.post_ban_cooldown; + return { + statusClass: 'rate-limited', + statusText: `Spotify recovering \u2014 ${formatRateLimitDuration(remaining)}`, + dotClass: 'rate-limited', + dotTitle: `Spotify recovering \u2014 ${formatRateLimitDuration(remaining)} cooldown`, + sessionActive + }; + } + + if (statusData?.source && statusData.source !== 'spotify') { + return { + statusClass: 'connected', + statusText: sourceLabel, + dotClass: 'connected', + dotTitle: sourceLabel, + sessionActive + }; + } + + return { + statusClass: 'connected', + statusText: `Connected (${statusData?.response_time}ms)`, + dotClass: 'connected', + dotTitle: '', + sessionActive + }; +} + function updateServiceStatus(service, statusData) { const indicator = document.getElementById(`${service}-status-indicator`); const statusText = document.getElementById(`${service}-status-text`); if (indicator && statusText) { - if (service === 'spotify' && (statusData.rate_limited || statusData.post_ban_cooldown)) { - indicator.className = 'service-card-indicator rate-limited'; - const remaining = statusData.rate_limited - ? formatRateLimitDuration(statusData.rate_limit?.remaining_seconds || 0) - : formatRateLimitDuration(statusData.post_ban_cooldown); - const phase = statusData.rate_limited ? 'paused' : 'recovering'; - const fallbackLabel = statusData.source === 'deezer' ? 'Deezer' : 'iTunes'; - statusText.textContent = `${fallbackLabel} (Spotify ${phase} \u2014 ${remaining})`; - statusText.className = 'service-card-status-text rate-limited'; - } else if (statusData.connected) { - indicator.className = 'service-card-indicator connected'; - statusText.textContent = `Connected (${statusData.response_time}ms)`; - statusText.className = 'service-card-status-text connected'; + if (service === 'spotify') { + const presentation = getSpotifyStatusPresentation(statusData || {}); + indicator.className = `service-card-indicator ${presentation.statusClass}`; + statusText.textContent = presentation.statusText; + statusText.className = `service-card-status-text ${presentation.statusClass}`; } else { - indicator.className = 'service-card-indicator disconnected'; - statusText.textContent = 'Disconnected'; - statusText.className = 'service-card-status-text disconnected'; + if (statusData.connected) { + indicator.className = 'service-card-indicator connected'; + statusText.textContent = `Connected (${statusData.response_time}ms)`; + statusText.className = 'service-card-status-text connected'; + } else { + indicator.className = 'service-card-indicator disconnected'; + statusText.textContent = 'Disconnected'; + statusText.className = 'service-card-status-text disconnected'; + } } } @@ -3220,13 +3273,13 @@ function updateServiceStatus(service, statusData) { if (service === 'spotify' && statusData.source) { const musicSourceTitleElement = document.getElementById('music-source-title'); if (musicSourceTitleElement) { - const sourceName = statusData.source === 'spotify' ? 'Spotify' : statusData.source === 'deezer' ? 'Deezer' : statusData.source === 'discogs' ? 'Discogs' : 'iTunes'; + const sourceName = getMetadataSourceLabel(statusData.source); musicSourceTitleElement.textContent = sourceName; currentMusicSourceName = sourceName; } // Keep the Spotify action buttons aligned with the actual auth session. - const spotifySessionActive = statusData.authenticated === true || (statusData.authenticated === undefined && statusData.source === 'spotify'); + const spotifySessionActive = getSpotifyStatusPresentation(statusData || {}).sessionActive; const authBtn = document.querySelector('button[onclick="authenticateSpotify()"]'); const disconnectBtn = document.getElementById('spotify-disconnect-btn'); if (authBtn) { @@ -3255,16 +3308,12 @@ function updateSidebarServiceStatus(service, statusData) { const nameElement = indicator.querySelector('.status-name'); if (dot) { - if (service === 'spotify' && (statusData.rate_limited || statusData.post_ban_cooldown)) { - dot.className = 'status-dot rate-limited'; - dot.title = statusData.rate_limited - ? `Spotify paused \u2014 ${formatRateLimitDuration(statusData.rate_limit?.remaining_seconds || 0)} remaining` - : `Spotify recovering \u2014 ${formatRateLimitDuration(statusData.post_ban_cooldown)} cooldown`; - } else if (statusData.connected) { - dot.className = 'status-dot connected'; - dot.title = ''; + if (service === 'spotify') { + const presentation = getSpotifyStatusPresentation(statusData || {}); + dot.className = `status-dot ${presentation.dotClass}`; + dot.title = presentation.dotTitle; } else { - dot.className = 'status-dot disconnected'; + dot.className = statusData?.connected ? 'status-dot connected' : 'status-dot disconnected'; dot.title = ''; } } @@ -3282,7 +3331,7 @@ function updateSidebarServiceStatus(service, statusData) { if (service === 'spotify' && statusData.source) { const musicSourceNameElement = document.getElementById('music-source-name'); if (musicSourceNameElement) { - const sourceName = statusData.source === 'spotify' ? 'Spotify' : statusData.source === 'deezer' ? 'Deezer' : statusData.source === 'discogs' ? 'Discogs' : 'iTunes'; + const sourceName = getMetadataSourceLabel(statusData.source); musicSourceNameElement.textContent = sourceName; } }