diff --git a/core/metadata_service.py b/core/metadata_service.py index 038bc132..2721a13d 100644 --- a/core/metadata_service.py +++ b/core/metadata_service.py @@ -31,6 +31,19 @@ def _create_fallback_client(): if source == 'deezer': from core.deezer_client import DeezerClient return DeezerClient() + if source == 'hydrabase': + try: + from core.hydrabase_client import HydrabaseClient + # Hydrabase client is managed globally โ€” try to import the running instance + import importlib + ws_module = importlib.import_module('web_server') + client = getattr(ws_module, 'hydrabase_client', None) + if client and client.is_connected(): + return client + except Exception: + pass + # Hydrabase not available โ€” fall back to iTunes + return iTunesClient() return iTunesClient() diff --git a/web_server.py b/web_server.py index 31c64115..2381739f 100644 --- a/web_server.py +++ b/web_server.py @@ -4481,7 +4481,7 @@ def handle_settings(): if 'active_media_server' in new_settings: config_manager.set_active_media_server(new_settings['active_media_server']) - for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata']: + for service in ['spotify', 'plex', 'jellyfin', 'navidrome', 'soulseek', 'download_source', 'settings', 'database', 'metadata_enhancement', 'file_organization', 'playlist_sync', 'tidal', 'tidal_download', 'qobuz', 'hifi_download', 'listenbrainz', 'acoustid', 'lastfm', 'genius', 'import', 'lossy_copy', 'ui_appearance', 'youtube', 'content_filter', 'itunes', 'm3u_export', 'musicbrainz', 'deezer', 'audiodb', 'metadata', 'hydrabase']: if service in new_settings: for key, value in new_settings[service].items(): config_manager.set(f'{service}.{key}', value) @@ -4560,15 +4560,15 @@ _comparison_lock = threading.Lock() def _is_hydrabase_active(): """Check if Hydrabase should be used as the primary metadata source. - Active when: (dev_mode OR hydrabase.enabled config) AND client connected.""" + Active when: (dev_mode OR selected as fallback source) AND client connected.""" try: if hydrabase_client is None or not hydrabase_client.is_connected(): return False # Dev mode always enables Hydrabase (legacy behavior) if dev_mode_enabled: return True - # Config toggle: user enabled Hydrabase as metadata source - return config_manager.get('hydrabase.enabled', False) + # Selected as the fallback metadata source + return _get_metadata_fallback_source() == 'hydrabase' except (NameError, Exception): return False @@ -4650,8 +4650,6 @@ def _run_background_comparison(query, hydrabase_counts=None): def hydrabase_connect(): """Connect to a Hydrabase instance via WebSocket.""" global _hydrabase_ws - if not dev_mode_enabled: - return jsonify({"success": False, "error": "Dev mode not active"}), 403 data = request.get_json() url = data.get('url', '').strip() api_key = data.get('api_key', '').strip() @@ -4694,8 +4692,10 @@ def hydrabase_disconnect(): pass _hydrabase_ws = None config_manager.set('hydrabase.auto_connect', False) - dev_mode_enabled = False - print("๐Ÿงช [Hydrabase] Disconnected โ€” dev mode disabled") + # Only disable dev mode if not using Hydrabase as a regular fallback source + if _get_metadata_fallback_source() != 'hydrabase': + dev_mode_enabled = False + print("๐Ÿงช [Hydrabase] Disconnected") return jsonify({"success": True}) @app.route('/api/hydrabase/status') @@ -27441,7 +27441,7 @@ def _get_deezer_client(): return _deezer_client_instance def _get_metadata_fallback_source(): - """Get the configured metadata fallback source ('itunes' or 'deezer').""" + """Get the configured metadata fallback source ('itunes', 'deezer', or 'hydrabase').""" try: return config_manager.get('metadata.fallback_source', 'itunes') or 'itunes' except Exception: @@ -27449,10 +27449,16 @@ def _get_metadata_fallback_source(): def _get_metadata_fallback_client(): """Get the active metadata fallback client based on settings. - Returns an iTunesClient or DeezerClient instance with identical interfaces.""" + Returns an iTunesClient, DeezerClient, or HydrabaseClient instance with identical interfaces.""" source = _get_metadata_fallback_source() if source == 'deezer': return _get_deezer_client() + if source == 'hydrabase': + if hydrabase_client and hydrabase_client.is_connected(): + return hydrabase_client + # Hydrabase not connected โ€” fall back to iTunes + from core.itunes_client import iTunesClient + return iTunesClient() from core.itunes_client import iTunesClient return iTunesClient() @@ -40772,9 +40778,8 @@ try: timeout=10 ) _hydrabase_ws = _auto_ws - # Enable dev mode only if Hydrabase was previously in dev mode - # The config toggle (hydrabase.enabled) handles non-dev usage - if not _hydra_cfg.get('enabled'): + # Enable dev mode only if not using Hydrabase as a regular fallback source + if _get_metadata_fallback_source() != 'hydrabase': dev_mode_enabled = True print(f"โœ… Hydrabase auto-connected to {_hydra_cfg['url']}") except Exception as e: diff --git a/webui/index.html b/webui/index.html index db1b3aa9..65cd88fa 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3673,19 +3673,14 @@
-
When Spotify is not connected, this source provides artist, album, and track metadata. Both are free and require no API key.
+
When Spotify is not connected, this source provides artist, album, and track metadata. Hydrabase requires a connection configured above.

Hydrabase

-
- -
+
diff --git a/webui/static/script.js b/webui/static/script.js index e3c323da..6cf65577 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -5469,10 +5469,30 @@ async function loadSettingsData() { // Populate Hydrabase settings const hbConfig = settings.hydrabase || {}; - document.getElementById('hydrabase-enabled').checked = hbConfig.enabled || false; document.getElementById('hydrabase-url').value = hbConfig.url || ''; document.getElementById('hydrabase-api-key').value = hbConfig.api_key || ''; document.getElementById('hydrabase-auto-connect').checked = hbConfig.auto_connect || false; + // Check live connection status + add Hydrabase to fallback dropdown if connected + fetch('/api/hydrabase/status').then(r => r.json()).then(s => { + const btn = document.getElementById('hydrabase-connect-btn'); + const statusEl = document.getElementById('hydrabase-settings-status'); + if (s.connected) { + if (btn) btn.textContent = 'Disconnect'; + if (statusEl) { statusEl.textContent = 'Connected'; statusEl.style.color = '#4caf50'; } + // Add Hydrabase to fallback source dropdown + const fbSelect = document.getElementById('metadata-fallback-source'); + if (fbSelect && !fbSelect.querySelector('option[value="hydrabase"]')) { + const opt = document.createElement('option'); + opt.value = 'hydrabase'; + opt.textContent = 'Hydrabase (P2P)'; + fbSelect.appendChild(opt); + } + // Restore selection if it was hydrabase + if ((settings.metadata?.fallback_source) === 'hydrabase') { + fbSelect.value = 'hydrabase'; + } + } + }).catch(() => {}); // Populate Download settings (right column) document.getElementById('download-path').value = settings.soulseek?.download_path || './downloads'; @@ -6107,7 +6127,16 @@ async function toggleHydrabaseFromSettings() { // Disconnect await fetch('/api/hydrabase/disconnect', { method: 'POST' }); if (btn) btn.textContent = 'Connect'; - if (statusEl) statusEl.textContent = 'Disconnected'; + if (statusEl) { statusEl.textContent = 'Disconnected'; statusEl.style.color = 'rgba(255,255,255,0.4)'; } + // Remove from fallback dropdown + reset to iTunes if was selected + const fbSel2 = document.getElementById('metadata-fallback-source'); + if (fbSel2) { + const hbOpt = fbSel2.querySelector('option[value="hydrabase"]'); + if (hbOpt) { + if (fbSel2.value === 'hydrabase') fbSel2.value = 'itunes'; + hbOpt.remove(); + } + } showToast('Hydrabase disconnected', 'info'); } else { // Connect @@ -6119,7 +6148,15 @@ async function toggleHydrabaseFromSettings() { const data = await res.json(); if (data.success) { if (btn) btn.textContent = 'Disconnect'; - if (statusEl) statusEl.textContent = 'Connected'; + if (statusEl) { statusEl.textContent = 'Connected'; statusEl.style.color = '#4caf50'; } + // Add to fallback dropdown + const fbSel = document.getElementById('metadata-fallback-source'); + if (fbSel && !fbSel.querySelector('option[value="hydrabase"]')) { + const opt = document.createElement('option'); + opt.value = 'hydrabase'; + opt.textContent = 'Hydrabase (P2P)'; + fbSel.appendChild(opt); + } showToast('Hydrabase connected', 'success'); } else { if (statusEl) statusEl.textContent = data.error || 'Connection failed'; @@ -6440,7 +6477,6 @@ async function saveSettings(quiet = false) { fallback_source: document.getElementById('metadata-fallback-source').value || 'itunes' }, hydrabase: { - enabled: document.getElementById('hydrabase-enabled').checked, url: document.getElementById('hydrabase-url').value, api_key: document.getElementById('hydrabase-api-key').value, auto_connect: document.getElementById('hydrabase-auto-connect').checked