Isolate service client initialization so one failure doesn't break the entire app
This commit is contained in:
parent
bc22bdca07
commit
e42f373f80
1 changed files with 84 additions and 33 deletions
117
web_server.py
117
web_server.py
|
|
@ -259,38 +259,81 @@ def _make_context_key(username, filename):
|
|||
return f"{username}::{normalized}"
|
||||
|
||||
# --- Initialize Core Application Components ---
|
||||
# Each client is initialized independently so one failure doesn't take down everything.
|
||||
# Previously, a single exception set ALL clients to None, breaking the entire app.
|
||||
print("🚀 Initializing SoulSync services for Web UI...")
|
||||
spotify_client = plex_client = jellyfin_client = navidrome_client = soulseek_client = tidal_client = matching_engine = sync_service = web_scan_manager = None
|
||||
|
||||
try:
|
||||
spotify_client = SpotifyClient()
|
||||
print(" ✅ Spotify client initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Spotify client failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
plex_client = PlexClient()
|
||||
print(" ✅ Plex client initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Plex client failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
jellyfin_client = JellyfinClient()
|
||||
print(" ✅ Jellyfin client initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Jellyfin client failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
navidrome_client = NavidromeClient()
|
||||
# Use DownloadOrchestrator instead of SoulseekClient directly (routes between Soulseek/YouTube)
|
||||
print(" ✅ Navidrome client initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Navidrome client failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
soulseek_client = DownloadOrchestrator()
|
||||
print(" ✅ Download orchestrator initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Download orchestrator failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
tidal_client = TidalClient()
|
||||
print(" ✅ Tidal client initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Tidal client failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
matching_engine = MusicMatchingEngine()
|
||||
print(" ✅ Matching engine initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Matching engine failed to initialize: {e}")
|
||||
|
||||
try:
|
||||
sync_service = PlaylistSyncService(spotify_client, plex_client, soulseek_client, jellyfin_client, navidrome_client)
|
||||
|
||||
# Inject shutdown check callback into YouTube and Tidal clients (avoids circular imports)
|
||||
# The callback uses the global IS_SHUTTING_DOWN flag from this module
|
||||
print(" ✅ Playlist sync service initialized")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Playlist sync service failed to initialize: {e}")
|
||||
|
||||
# Inject shutdown check callback into YouTube and Tidal clients (avoids circular imports)
|
||||
if soulseek_client:
|
||||
if hasattr(soulseek_client, 'youtube'):
|
||||
soulseek_client.youtube.set_shutdown_check(lambda: IS_SHUTTING_DOWN)
|
||||
print("✅ Configured YouTube client shutdown callback")
|
||||
print(" ✅ Configured YouTube client shutdown callback")
|
||||
if hasattr(soulseek_client, 'tidal'):
|
||||
soulseek_client.tidal.set_shutdown_check(lambda: IS_SHUTTING_DOWN)
|
||||
print("✅ Configured Tidal download client shutdown callback")
|
||||
print(" ✅ Configured Tidal download client shutdown callback")
|
||||
|
||||
# Initialize web scan manager for automatic post-download scanning
|
||||
# Initialize web scan manager for automatic post-download scanning
|
||||
try:
|
||||
media_clients = {
|
||||
'plex_client': plex_client,
|
||||
'jellyfin_client': jellyfin_client,
|
||||
'navidrome_client': navidrome_client
|
||||
}
|
||||
web_scan_manager = WebScanManager(media_clients, delay_seconds=60)
|
||||
print("✅ Core service clients and scan manager initialized.")
|
||||
print(" ✅ Web scan manager initialized")
|
||||
except Exception as e:
|
||||
print(f"🔴 FATAL: Error initializing service clients: {e}")
|
||||
spotify_client = plex_client = jellyfin_client = navidrome_client = soulseek_client = tidal_client = matching_engine = sync_service = web_scan_manager = None
|
||||
print(f" ⚠️ Web scan manager failed to initialize: {e}")
|
||||
|
||||
print("✅ Core service initialization complete.")
|
||||
|
||||
# --- Automation Engine ---
|
||||
try:
|
||||
|
|
@ -3519,8 +3562,8 @@ STATUS_CACHE_TTL = 120 # Cache for 2 minutes (reduces API calls while staying f
|
|||
|
||||
@app.route('/status')
|
||||
def get_status():
|
||||
if not all([spotify_client, plex_client, jellyfin_client, soulseek_client, config_manager]):
|
||||
return jsonify({"error": "Core services not initialized."}), 500
|
||||
if not config_manager:
|
||||
return jsonify({"error": "Server configuration manager is not initialized."}), 500
|
||||
try:
|
||||
import time
|
||||
|
||||
|
|
@ -3529,11 +3572,10 @@ def get_status():
|
|||
|
||||
# Test Spotify - with caching to avoid excessive API calls
|
||||
if current_time - _status_cache_timestamps['spotify'] > STATUS_CACHE_TTL:
|
||||
# Check is_rate_limited() first — this calls _is_globally_rate_limited() which
|
||||
# records ban-end timestamp if the ban just expired, enabling cooldown detection.
|
||||
is_rate_limited = spotify_client.is_rate_limited()
|
||||
rate_limit_info = spotify_client.get_rate_limit_info() if is_rate_limited else None
|
||||
cooldown_remaining = spotify_client.get_post_ban_cooldown_remaining()
|
||||
# Guard against spotify_client being None (partial init)
|
||||
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
|
||||
cooldown_remaining = spotify_client.get_post_ban_cooldown_remaining() if spotify_client else 0
|
||||
|
||||
if is_rate_limited or cooldown_remaining > 0:
|
||||
# During rate limit or post-ban cooldown, skip the auth probe entirely.
|
||||
|
|
@ -3542,7 +3584,7 @@ def get_status():
|
|||
spotify_response_time = 0
|
||||
else:
|
||||
spotify_start = time.time()
|
||||
spotify_connected = spotify_client.is_spotify_authenticated()
|
||||
spotify_connected = spotify_client.is_spotify_authenticated() if spotify_client else False
|
||||
spotify_response_time = (time.time() - spotify_start) * 1000
|
||||
music_source = 'spotify' if spotify_connected else 'itunes'
|
||||
|
||||
|
|
@ -3561,13 +3603,13 @@ def get_status():
|
|||
if current_time - _status_cache_timestamps['media_server'] > STATUS_CACHE_TTL:
|
||||
media_server_start = time.time()
|
||||
media_server_status = False
|
||||
if active_server == "plex":
|
||||
if active_server == "plex" and plex_client:
|
||||
# Use existing instance - has 30s internal connection cache
|
||||
media_server_status = plex_client.is_connected()
|
||||
elif active_server == "jellyfin":
|
||||
elif active_server == "jellyfin" and jellyfin_client:
|
||||
# Use existing instance - has internal connection caching
|
||||
media_server_status = jellyfin_client.is_connected()
|
||||
elif active_server == "navidrome":
|
||||
elif active_server == "navidrome" and navidrome_client:
|
||||
# Use existing instance
|
||||
media_server_status = navidrome_client.is_connected()
|
||||
media_server_response_time = (time.time() - media_server_start) * 1000
|
||||
|
|
@ -3583,7 +3625,7 @@ def get_status():
|
|||
if current_time - _status_cache_timestamps['soulseek'] > STATUS_CACHE_TTL:
|
||||
soulseek_start = time.time()
|
||||
try:
|
||||
soulseek_status = run_async(soulseek_client.check_connection())
|
||||
soulseek_status = run_async(soulseek_client.check_connection()) if soulseek_client else False
|
||||
except Exception:
|
||||
soulseek_status = False
|
||||
soulseek_response_time = (time.time() - soulseek_start) * 1000
|
||||
|
|
@ -3966,17 +4008,26 @@ def handle_settings():
|
|||
|
||||
add_activity_item("⚙️", "Settings Updated", f"{services_text} configuration saved", "Now")
|
||||
|
||||
spotify_client.reload_config()
|
||||
plex_client.server = None
|
||||
jellyfin_client.reload_config()
|
||||
navidrome_client.reload_config()
|
||||
# Reload service clients with new settings (guard against None from partial init)
|
||||
if spotify_client:
|
||||
spotify_client.reload_config()
|
||||
if plex_client:
|
||||
plex_client.server = None
|
||||
if jellyfin_client:
|
||||
jellyfin_client.reload_config()
|
||||
if navidrome_client:
|
||||
navidrome_client.reload_config()
|
||||
# Reload orchestrator settings (download source mode, hybrid_primary, etc.)
|
||||
soulseek_client.reload_settings()
|
||||
# Reload YouTube client settings (rate limiting, cookies)
|
||||
if hasattr(soulseek_client, 'youtube'):
|
||||
soulseek_client.youtube.reload_settings()
|
||||
if soulseek_client:
|
||||
soulseek_client.reload_settings()
|
||||
# Reload YouTube client settings (rate limiting, cookies)
|
||||
if hasattr(soulseek_client, 'youtube'):
|
||||
soulseek_client.youtube.reload_settings()
|
||||
# FIX: Re-instantiate the global tidal_client to pick up new settings
|
||||
tidal_client = TidalClient()
|
||||
try:
|
||||
tidal_client = TidalClient()
|
||||
except Exception:
|
||||
pass # Keep existing tidal_client if re-init fails
|
||||
# Reload enrichment worker clients for key-based services
|
||||
if lastfm_worker:
|
||||
lastfm_worker._init_client()
|
||||
|
|
@ -4789,7 +4840,7 @@ def test_connection_endpoint():
|
|||
current_time = time.time()
|
||||
if service == 'spotify':
|
||||
_status_cache['spotify']['connected'] = True
|
||||
_status_cache['spotify']['source'] = 'spotify' if spotify_client.is_spotify_authenticated() else 'itunes'
|
||||
_status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else 'itunes'
|
||||
_status_cache_timestamps['spotify'] = current_time
|
||||
print("✅ Updated Spotify status cache after successful test")
|
||||
elif service in ['plex', 'jellyfin', 'navidrome']:
|
||||
|
|
@ -4839,7 +4890,7 @@ def test_dashboard_connection_endpoint():
|
|||
current_time = time.time()
|
||||
if service == 'spotify':
|
||||
_status_cache['spotify']['connected'] = True
|
||||
_status_cache['spotify']['source'] = 'spotify' if spotify_client.is_spotify_authenticated() else 'itunes'
|
||||
_status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else 'itunes'
|
||||
_status_cache_timestamps['spotify'] = current_time
|
||||
print("✅ Updated Spotify status cache after successful dashboard test")
|
||||
elif service in ['plex', 'jellyfin', 'navidrome']:
|
||||
|
|
|
|||
Loading…
Reference in a new issue