MS Cin-3 + Cin-4: Route web_server through engine instead of per-client globals
Pre-change web_server.py had ~70 direct attribute reaches against the
per-server globals (plex_client.X, jellyfin_client.X, navidrome_client.X,
soulsync_library_client.X) plus ~60 standalone refs (truthy checks,
media_client assignments, source-name tuples). The engine was wired
but only used in 4 places, so most of the codebase still hand-dispatched
— the exact "partially defeats the purpose of this refactor" critique
Cin landed on the download PR initially.
- All ~70 client.attribute reaches migrated to
media_server_engine.client('<name>').attribute. The chains in
web_server.py do server-specific work (Plex raw API, Jellyfin /
Navidrome client methods, all returning different shapes), so the
if/elif structure stays — but the per-server CLIENT REACH now goes
through the engine like Cin's POC pattern intended.
- All ~60 standalone refs migrated:
- if plex_client → if media_server_engine.client('plex')
- media_client = plex_client → media_client = media_server_engine.client('plex')
- ('plex', plex_client) tuples → ('plex', media_server_engine.client('plex'))
- Per-server globals (plex_client / jellyfin_client / navidrome_client /
soulsync_library_client) kept for now — external modules
(PlaylistSyncService, WebScanManager, ListeningStatsWorker, search
library check, discovery sync deps) still take them as kwargs.
Dropping them entirely needs a follow-up sweep across those modules.
Suite green (1961 pass).
This commit is contained in:
parent
49f7679eef
commit
1bc5017592
1 changed files with 110 additions and 110 deletions
220
web_server.py
220
web_server.py
|
|
@ -644,7 +644,7 @@ except Exception as e:
|
||||||
logger.error(f" Matching engine failed to initialize: {e}")
|
logger.error(f" Matching engine failed to initialize: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sync_service = PlaylistSyncService(spotify_client, plex_client, soulseek_client, jellyfin_client, navidrome_client)
|
sync_service = PlaylistSyncService(spotify_client, plex_client, soulseek_client, jellyfin_client, media_server_engine.client('navidrome'))
|
||||||
logger.info(" Playlist sync service initialized")
|
logger.info(" Playlist sync service initialized")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f" Playlist sync service failed to initialize: {e}")
|
logger.error(f" Playlist sync service failed to initialize: {e}")
|
||||||
|
|
@ -4154,12 +4154,12 @@ def handle_settings():
|
||||||
# Reload service clients with new settings (guard against None from partial init)
|
# Reload service clients with new settings (guard against None from partial init)
|
||||||
if spotify_client:
|
if spotify_client:
|
||||||
spotify_client.reload_config()
|
spotify_client.reload_config()
|
||||||
if plex_client:
|
if media_server_engine.client('plex'):
|
||||||
plex_client.server = None
|
media_server_engine.client('plex').server = None
|
||||||
if jellyfin_client:
|
if media_server_engine.client('jellyfin'):
|
||||||
jellyfin_client.reload_config()
|
media_server_engine.client('jellyfin').reload_config()
|
||||||
if navidrome_client:
|
if media_server_engine.client('navidrome'):
|
||||||
navidrome_client.reload_config()
|
media_server_engine.client('navidrome').reload_config()
|
||||||
# Reload orchestrator settings (download source mode, hybrid_primary, etc.)
|
# Reload orchestrator settings (download source mode, hybrid_primary, etc.)
|
||||||
if soulseek_client:
|
if soulseek_client:
|
||||||
soulseek_client.reload_settings()
|
soulseek_client.reload_settings()
|
||||||
|
|
@ -5122,8 +5122,8 @@ def clear_plex_library_preference():
|
||||||
from database.music_database import MusicDatabase
|
from database.music_database import MusicDatabase
|
||||||
db = MusicDatabase()
|
db = MusicDatabase()
|
||||||
db.set_preference('plex_music_library', '')
|
db.set_preference('plex_music_library', '')
|
||||||
if plex_client:
|
if media_server_engine.client('plex'):
|
||||||
plex_client.music_library = None
|
media_server_engine.client('plex').music_library = None
|
||||||
return jsonify({"success": True, "message": "Plex library preference cleared."})
|
return jsonify({"success": True, "message": "Plex library preference cleared."})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error clearing Plex library preference: {e}")
|
logger.error(f"Error clearing Plex library preference: {e}")
|
||||||
|
|
@ -5134,7 +5134,7 @@ def clear_plex_library_preference():
|
||||||
def get_plex_music_libraries():
|
def get_plex_music_libraries():
|
||||||
"""Get list of all available music libraries from Plex"""
|
"""Get list of all available music libraries from Plex"""
|
||||||
try:
|
try:
|
||||||
libraries = plex_client.get_available_music_libraries()
|
libraries = media_server_engine.client('plex').get_available_music_libraries()
|
||||||
|
|
||||||
# Get currently selected library
|
# Get currently selected library
|
||||||
from database.music_database import MusicDatabase
|
from database.music_database import MusicDatabase
|
||||||
|
|
@ -5143,8 +5143,8 @@ def get_plex_music_libraries():
|
||||||
|
|
||||||
# Get the currently active library name
|
# Get the currently active library name
|
||||||
current_library = None
|
current_library = None
|
||||||
if plex_client.music_library:
|
if media_server_engine.client('plex').music_library:
|
||||||
current_library = plex_client.music_library.title
|
current_library = media_server_engine.client('plex').music_library.title
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
@ -5166,7 +5166,7 @@ def select_plex_music_library():
|
||||||
if not library_name:
|
if not library_name:
|
||||||
return jsonify({"success": False, "error": "No library name provided"}), 400
|
return jsonify({"success": False, "error": "No library name provided"}), 400
|
||||||
|
|
||||||
success = plex_client.set_music_library_by_name(library_name)
|
success = media_server_engine.client('plex').set_music_library_by_name(library_name)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
add_activity_item("", "Library Selected", f"Plex music library set to: {library_name}", "Now")
|
add_activity_item("", "Library Selected", f"Plex music library set to: {library_name}", "Now")
|
||||||
|
|
@ -5182,7 +5182,7 @@ def select_plex_music_library():
|
||||||
def get_jellyfin_users():
|
def get_jellyfin_users():
|
||||||
"""Get list of Jellyfin users that have music libraries"""
|
"""Get list of Jellyfin users that have music libraries"""
|
||||||
try:
|
try:
|
||||||
users = jellyfin_client.get_available_users()
|
users = media_server_engine.client('jellyfin').get_available_users()
|
||||||
|
|
||||||
# Get currently selected user
|
# Get currently selected user
|
||||||
from database.music_database import MusicDatabase
|
from database.music_database import MusicDatabase
|
||||||
|
|
@ -5191,9 +5191,9 @@ def get_jellyfin_users():
|
||||||
|
|
||||||
# Determine the current user name from user_id
|
# Determine the current user name from user_id
|
||||||
current_user = None
|
current_user = None
|
||||||
if jellyfin_client.user_id:
|
if media_server_engine.client('jellyfin').user_id:
|
||||||
for u in users:
|
for u in users:
|
||||||
if u['id'] == jellyfin_client.user_id:
|
if u['id'] == media_server_engine.client('jellyfin').user_id:
|
||||||
current_user = u['name']
|
current_user = u['name']
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -5217,7 +5217,7 @@ def select_jellyfin_user():
|
||||||
if not username:
|
if not username:
|
||||||
return jsonify({"success": False, "error": "No username provided"}), 400
|
return jsonify({"success": False, "error": "No username provided"}), 400
|
||||||
|
|
||||||
success = jellyfin_client.set_user_by_name(username)
|
success = media_server_engine.client('jellyfin').set_user_by_name(username)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
add_activity_item("", "User Selected", f"Jellyfin user set to: {username}", "Now")
|
add_activity_item("", "User Selected", f"Jellyfin user set to: {username}", "Now")
|
||||||
|
|
@ -5233,7 +5233,7 @@ def select_jellyfin_user():
|
||||||
def get_jellyfin_music_libraries():
|
def get_jellyfin_music_libraries():
|
||||||
"""Get list of all available music libraries from Jellyfin"""
|
"""Get list of all available music libraries from Jellyfin"""
|
||||||
try:
|
try:
|
||||||
libraries = jellyfin_client.get_available_music_libraries()
|
libraries = media_server_engine.client('jellyfin').get_available_music_libraries()
|
||||||
|
|
||||||
# Get currently selected library
|
# Get currently selected library
|
||||||
from database.music_database import MusicDatabase
|
from database.music_database import MusicDatabase
|
||||||
|
|
@ -5242,10 +5242,10 @@ def get_jellyfin_music_libraries():
|
||||||
|
|
||||||
# Get the currently active library name (match Plex behavior)
|
# Get the currently active library name (match Plex behavior)
|
||||||
current_library = None
|
current_library = None
|
||||||
if jellyfin_client.music_library_id:
|
if media_server_engine.client('jellyfin').music_library_id:
|
||||||
# Look up library name from ID
|
# Look up library name from ID
|
||||||
for lib in libraries:
|
for lib in libraries:
|
||||||
if lib['key'] == jellyfin_client.music_library_id:
|
if lib['key'] == media_server_engine.client('jellyfin').music_library_id:
|
||||||
current_library = lib['title']
|
current_library = lib['title']
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -5269,7 +5269,7 @@ def select_jellyfin_music_library():
|
||||||
if not library_name:
|
if not library_name:
|
||||||
return jsonify({"success": False, "error": "No library name provided"}), 400
|
return jsonify({"success": False, "error": "No library name provided"}), 400
|
||||||
|
|
||||||
success = jellyfin_client.set_music_library_by_name(library_name)
|
success = media_server_engine.client('jellyfin').set_music_library_by_name(library_name)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
add_activity_item("", "Library Selected", f"Jellyfin music library set to: {library_name}", "Now")
|
add_activity_item("", "Library Selected", f"Jellyfin music library set to: {library_name}", "Now")
|
||||||
|
|
@ -5285,19 +5285,19 @@ def select_jellyfin_music_library():
|
||||||
def get_navidrome_music_folders():
|
def get_navidrome_music_folders():
|
||||||
"""Get list of available music folders from Navidrome"""
|
"""Get list of available music folders from Navidrome"""
|
||||||
try:
|
try:
|
||||||
if not navidrome_client:
|
if not media_server_engine.client('navidrome'):
|
||||||
return jsonify({"success": False, "error": "Navidrome client not configured"}), 400
|
return jsonify({"success": False, "error": "Navidrome client not configured"}), 400
|
||||||
|
|
||||||
folders = navidrome_client.get_music_folders()
|
folders = media_server_engine.client('navidrome').get_music_folders()
|
||||||
|
|
||||||
from database.music_database import MusicDatabase
|
from database.music_database import MusicDatabase
|
||||||
db = MusicDatabase()
|
db = MusicDatabase()
|
||||||
selected_folder = db.get_preference('navidrome_music_folder')
|
selected_folder = db.get_preference('navidrome_music_folder')
|
||||||
|
|
||||||
current_folder = None
|
current_folder = None
|
||||||
if navidrome_client.music_folder_id:
|
if media_server_engine.client('navidrome').music_folder_id:
|
||||||
for f in folders:
|
for f in folders:
|
||||||
if f['key'] == navidrome_client.music_folder_id:
|
if f['key'] == media_server_engine.client('navidrome').music_folder_id:
|
||||||
current_folder = f['title']
|
current_folder = f['title']
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -5318,7 +5318,7 @@ def select_navidrome_music_folder():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
folder_name = data.get('folder_name', '')
|
folder_name = data.get('folder_name', '')
|
||||||
|
|
||||||
success = navidrome_client.set_music_folder_by_name(folder_name)
|
success = media_server_engine.client('navidrome').set_music_folder_by_name(folder_name)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
if folder_name:
|
if folder_name:
|
||||||
|
|
@ -8046,9 +8046,9 @@ def test_automation_workflow():
|
||||||
results['media_clients'] = {'active_server': active_server}
|
results['media_clients'] = {'active_server': active_server}
|
||||||
|
|
||||||
for client_name, client in [
|
for client_name, client in [
|
||||||
('plex', plex_client),
|
('plex', media_server_engine.client('plex')),
|
||||||
('jellyfin', jellyfin_client),
|
('jellyfin', media_server_engine.client('jellyfin')),
|
||||||
('navidrome', navidrome_client)
|
('navidrome', media_server_engine.client('navidrome'))
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
is_connected = client.is_connected() if client else False
|
is_connected = client.is_connected() if client else False
|
||||||
|
|
@ -10810,7 +10810,7 @@ def _sync_tracks_to_server(track_rows, server_type):
|
||||||
if track_data.get('year'):
|
if track_data.get('year'):
|
||||||
metadata['year'] = track_data['year']
|
metadata['year'] = track_data['year']
|
||||||
if metadata:
|
if metadata:
|
||||||
success = plex_client.update_track_metadata(str(track_data['id']), metadata)
|
success = media_server_engine.client('plex').update_track_metadata(str(track_data['id']), metadata)
|
||||||
if success:
|
if success:
|
||||||
result['synced'] += 1
|
result['synced'] += 1
|
||||||
else:
|
else:
|
||||||
|
|
@ -10825,7 +10825,7 @@ def _sync_tracks_to_server(track_rows, server_type):
|
||||||
elif server_type == 'jellyfin':
|
elif server_type == 'jellyfin':
|
||||||
# Jellyfin: just trigger a library scan once after all file writes
|
# Jellyfin: just trigger a library scan once after all file writes
|
||||||
try:
|
try:
|
||||||
success = jellyfin_client.trigger_library_scan()
|
success = media_server_engine.client('jellyfin').trigger_library_scan()
|
||||||
if success:
|
if success:
|
||||||
result['synced'] = len(track_rows)
|
result['synced'] = len(track_rows)
|
||||||
else:
|
else:
|
||||||
|
|
@ -10853,8 +10853,8 @@ def _resolve_library_file_path(file_path):
|
||||||
# Also check the media server's music library path (handles Docker↔host path mismatch)
|
# Also check the media server's music library path (handles Docker↔host path mismatch)
|
||||||
library_dirs = set()
|
library_dirs = set()
|
||||||
try:
|
try:
|
||||||
if plex_client and plex_client.server and plex_client.music_library:
|
if media_server_engine.client('plex') and media_server_engine.client('plex').server and media_server_engine.client('plex').music_library:
|
||||||
for loc in plex_client.music_library.locations:
|
for loc in media_server_engine.client('plex').music_library.locations:
|
||||||
library_dirs.add(loc)
|
library_dirs.add(loc)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -11526,9 +11526,9 @@ def redownload_search_metadata(track_id):
|
||||||
if thumb_url and not thumb_url.startswith('http'):
|
if thumb_url and not thumb_url.startswith('http'):
|
||||||
_ab = ''
|
_ab = ''
|
||||||
_at = ''
|
_at = ''
|
||||||
if plex_client and plex_client.server:
|
if media_server_engine.client('plex') and media_server_engine.client('plex').server:
|
||||||
_ab = getattr(plex_client.server, '_baseurl', '') or ''
|
_ab = getattr(media_server_engine.client('plex').server, '_baseurl', '') or ''
|
||||||
_at = getattr(plex_client.server, '_token', '') or ''
|
_at = getattr(media_server_engine.client('plex').server, '_token', '') or ''
|
||||||
if not _ab:
|
if not _ab:
|
||||||
_pc = config_manager.get_plex_config()
|
_pc = config_manager.get_plex_config()
|
||||||
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
||||||
|
|
@ -11834,12 +11834,12 @@ def sync_artist_library(artist_id):
|
||||||
|
|
||||||
if server_source:
|
if server_source:
|
||||||
media_client = None
|
media_client = None
|
||||||
if server_source == 'plex' and plex_client and plex_client.server:
|
if server_source == 'plex' and media_server_engine.client('plex') and media_server_engine.client('plex').server:
|
||||||
media_client = plex_client
|
media_client = media_server_engine.client('plex')
|
||||||
elif server_source == 'jellyfin' and jellyfin_client:
|
elif server_source == 'jellyfin' and media_server_engine.client('jellyfin'):
|
||||||
media_client = jellyfin_client
|
media_client = media_server_engine.client('jellyfin')
|
||||||
elif server_source == 'navidrome' and navidrome_client:
|
elif server_source == 'navidrome' and media_server_engine.client('navidrome'):
|
||||||
media_client = navidrome_client
|
media_client = media_server_engine.client('navidrome')
|
||||||
|
|
||||||
if media_client:
|
if media_client:
|
||||||
try:
|
try:
|
||||||
|
|
@ -15122,11 +15122,11 @@ def _run_db_update_task(full_refresh, server_type):
|
||||||
media_client = None
|
media_client = None
|
||||||
|
|
||||||
if server_type == "plex":
|
if server_type == "plex":
|
||||||
media_client = plex_client
|
media_client = media_server_engine.client('plex')
|
||||||
elif server_type == "jellyfin":
|
elif server_type == "jellyfin":
|
||||||
media_client = jellyfin_client
|
media_client = media_server_engine.client('jellyfin')
|
||||||
elif server_type == "navidrome":
|
elif server_type == "navidrome":
|
||||||
media_client = navidrome_client
|
media_client = media_server_engine.client('navidrome')
|
||||||
|
|
||||||
if not media_client:
|
if not media_client:
|
||||||
_db_update_error_callback(f"Media client for '{server_type}' not available.")
|
_db_update_error_callback(f"Media client for '{server_type}' not available.")
|
||||||
|
|
@ -15168,11 +15168,11 @@ def _run_deep_scan_task(server_type):
|
||||||
media_client = None
|
media_client = None
|
||||||
|
|
||||||
if server_type == "plex":
|
if server_type == "plex":
|
||||||
media_client = plex_client
|
media_client = media_server_engine.client('plex')
|
||||||
elif server_type == "jellyfin":
|
elif server_type == "jellyfin":
|
||||||
media_client = jellyfin_client
|
media_client = media_server_engine.client('jellyfin')
|
||||||
elif server_type == "navidrome":
|
elif server_type == "navidrome":
|
||||||
media_client = navidrome_client
|
media_client = media_server_engine.client('navidrome')
|
||||||
elif server_type == "soulsync":
|
elif server_type == "soulsync":
|
||||||
# SoulSync standalone deep scan: find untracked files → move to Staging,
|
# SoulSync standalone deep scan: find untracked files → move to Staging,
|
||||||
# remove stale DB records where files no longer exist on disk
|
# remove stale DB records where files no longer exist on disk
|
||||||
|
|
@ -18045,10 +18045,10 @@ def get_server_playlists():
|
||||||
return jsonify({"success": False, "error": "No media server configured"}), 400
|
return jsonify({"success": False, "error": "No media server configured"}), 400
|
||||||
|
|
||||||
playlists_data = []
|
playlists_data = []
|
||||||
if active_server == 'plex' and plex_client and plex_client.is_connected():
|
if active_server == 'plex' and media_server_engine.client('plex') and media_server_engine.client('plex').is_connected():
|
||||||
# Use raw Plex API to get playlist metadata without fetching all tracks
|
# Use raw Plex API to get playlist metadata without fetching all tracks
|
||||||
try:
|
try:
|
||||||
raw_playlists = plex_client.server.playlists()
|
raw_playlists = media_server_engine.client('plex').server.playlists()
|
||||||
logger.info(f"[ServerPlaylists] Plex returned {len(raw_playlists)} total playlists")
|
logger.info(f"[ServerPlaylists] Plex returned {len(raw_playlists)} total playlists")
|
||||||
for playlist in raw_playlists:
|
for playlist in raw_playlists:
|
||||||
if getattr(playlist, 'playlistType', None) == 'audio':
|
if getattr(playlist, 'playlistType', None) == 'audio':
|
||||||
|
|
@ -18061,22 +18061,22 @@ def get_server_playlists():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[ServerPlaylists] Error fetching Plex playlists: {e}", exc_info=True)
|
logger.error(f"[ServerPlaylists] Error fetching Plex playlists: {e}", exc_info=True)
|
||||||
return jsonify({"success": False, "error": f"Plex error: {str(e)}"}), 500
|
return jsonify({"success": False, "error": f"Plex error: {str(e)}"}), 500
|
||||||
elif active_server == 'jellyfin' and jellyfin_client and jellyfin_client.is_connected():
|
elif active_server == 'jellyfin' and media_server_engine.client('jellyfin') and media_server_engine.client('jellyfin').is_connected():
|
||||||
for pl in jellyfin_client.get_all_playlists():
|
for pl in media_server_engine.client('jellyfin').get_all_playlists():
|
||||||
playlists_data.append({
|
playlists_data.append({
|
||||||
'id': pl.id,
|
'id': pl.id,
|
||||||
'name': pl.title,
|
'name': pl.title,
|
||||||
'track_count': pl.leaf_count,
|
'track_count': pl.leaf_count,
|
||||||
})
|
})
|
||||||
elif active_server == 'navidrome' and navidrome_client and navidrome_client.is_connected():
|
elif active_server == 'navidrome' and media_server_engine.client('navidrome') and media_server_engine.client('navidrome').is_connected():
|
||||||
for pl in navidrome_client.get_all_playlists():
|
for pl in media_server_engine.client('navidrome').get_all_playlists():
|
||||||
playlists_data.append({
|
playlists_data.append({
|
||||||
'id': pl.id,
|
'id': pl.id,
|
||||||
'name': pl.title,
|
'name': pl.title,
|
||||||
'track_count': pl.leaf_count,
|
'track_count': pl.leaf_count,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
logger.warning(f"[ServerPlaylists] Server '{active_server}' not connected. plex_client={plex_client is not None}, jellyfin_client={jellyfin_client is not None}, navidrome_client={navidrome_client is not None}")
|
logger.warning(f"[ServerPlaylists] Server '{active_server}' not connected. plex_client={media_server_engine.client('plex') is not None}, jellyfin_client={media_server_engine.client('jellyfin') is not None}, navidrome_client={media_server_engine.client('navidrome') is not None}")
|
||||||
return jsonify({"success": False, "error": f"{active_server} not connected"}), 400
|
return jsonify({"success": False, "error": f"{active_server} not connected"}), 400
|
||||||
|
|
||||||
return jsonify({"success": True, "server_type": active_server, "playlists": playlists_data})
|
return jsonify({"success": True, "server_type": active_server, "playlists": playlists_data})
|
||||||
|
|
@ -18094,24 +18094,24 @@ def get_server_playlist_tracks(playlist_id):
|
||||||
|
|
||||||
# Get tracks from server
|
# Get tracks from server
|
||||||
server_tracks = []
|
server_tracks = []
|
||||||
if active_server == 'plex' and plex_client:
|
if active_server == 'plex' and media_server_engine.client('plex'):
|
||||||
try:
|
try:
|
||||||
# Try by ID first, fall back to name lookup (ID changes when playlist is recreated)
|
# Try by ID first, fall back to name lookup (ID changes when playlist is recreated)
|
||||||
raw_playlist = None
|
raw_playlist = None
|
||||||
try:
|
try:
|
||||||
raw_playlist = plex_client.server.fetchItem(int(playlist_id))
|
raw_playlist = media_server_engine.client('plex').server.fetchItem(int(playlist_id))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if not raw_playlist and playlist_name:
|
if not raw_playlist and playlist_name:
|
||||||
try:
|
try:
|
||||||
raw_playlist = plex_client.server.playlist(playlist_name)
|
raw_playlist = media_server_engine.client('plex').server.playlist(playlist_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if raw_playlist:
|
if raw_playlist:
|
||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
playlist_name = raw_playlist.title
|
playlist_name = raw_playlist.title
|
||||||
plex_base = getattr(plex_client.server, '_baseurl', '') or ''
|
plex_base = getattr(media_server_engine.client('plex').server, '_baseurl', '') or ''
|
||||||
plex_token = getattr(plex_client.server, '_token', '') or ''
|
plex_token = getattr(media_server_engine.client('plex').server, '_token', '') or ''
|
||||||
if not plex_base:
|
if not plex_base:
|
||||||
# Fallback: get from config
|
# Fallback: get from config
|
||||||
_pc = config_manager.get_plex_config()
|
_pc = config_manager.get_plex_config()
|
||||||
|
|
@ -18136,9 +18136,9 @@ def get_server_playlist_tracks(playlist_id):
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[ServerPlaylistTracks] Plex error: {e}", exc_info=True)
|
logger.error(f"[ServerPlaylistTracks] Plex error: {e}", exc_info=True)
|
||||||
elif active_server == 'jellyfin' and jellyfin_client:
|
elif active_server == 'jellyfin' and media_server_engine.client('jellyfin'):
|
||||||
tracks = jellyfin_client.get_playlist_tracks(playlist_id)
|
tracks = media_server_engine.client('jellyfin').get_playlist_tracks(playlist_id)
|
||||||
jf_base = jellyfin_client.base_url or ''
|
jf_base = media_server_engine.client('jellyfin').base_url or ''
|
||||||
for t in (tracks or []):
|
for t in (tracks or []):
|
||||||
raw = t._data if hasattr(t, '_data') else {}
|
raw = t._data if hasattr(t, '_data') else {}
|
||||||
artists = raw.get('Artists', [])
|
artists = raw.get('Artists', [])
|
||||||
|
|
@ -18153,8 +18153,8 @@ def get_server_playlist_tracks(playlist_id):
|
||||||
'duration': t.duration,
|
'duration': t.duration,
|
||||||
'thumb': thumb,
|
'thumb': thumb,
|
||||||
})
|
})
|
||||||
elif active_server == 'navidrome' and navidrome_client:
|
elif active_server == 'navidrome' and media_server_engine.client('navidrome'):
|
||||||
tracks = navidrome_client.get_playlist_tracks(playlist_id)
|
tracks = media_server_engine.client('navidrome').get_playlist_tracks(playlist_id)
|
||||||
for t in (tracks or []):
|
for t in (tracks or []):
|
||||||
raw = t._data if hasattr(t, '_data') else {}
|
raw = t._data if hasattr(t, '_data') else {}
|
||||||
# Navidrome cover art via Subsonic API
|
# Navidrome cover art via Subsonic API
|
||||||
|
|
@ -18179,9 +18179,9 @@ def get_server_playlist_tracks(playlist_id):
|
||||||
# Build server art URL prefix for resolving relative thumb paths
|
# Build server art URL prefix for resolving relative thumb paths
|
||||||
_art_prefix = ''
|
_art_prefix = ''
|
||||||
_art_suffix = ''
|
_art_suffix = ''
|
||||||
if active_server == 'plex' and plex_client and plex_client.server:
|
if active_server == 'plex' and media_server_engine.client('plex') and media_server_engine.client('plex').server:
|
||||||
_ab = getattr(plex_client.server, '_baseurl', '') or ''
|
_ab = getattr(media_server_engine.client('plex').server, '_baseurl', '') or ''
|
||||||
_at = getattr(plex_client.server, '_token', '') or ''
|
_at = getattr(media_server_engine.client('plex').server, '_token', '') or ''
|
||||||
if not _ab:
|
if not _ab:
|
||||||
_pc = config_manager.get_plex_config()
|
_pc = config_manager.get_plex_config()
|
||||||
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
||||||
|
|
@ -18364,10 +18364,10 @@ def server_playlist_replace_track(playlist_id):
|
||||||
|
|
||||||
active_server = config_manager.get_active_media_server()
|
active_server = config_manager.get_active_media_server()
|
||||||
|
|
||||||
if active_server == 'plex' and plex_client:
|
if active_server == 'plex' and media_server_engine.client('plex'):
|
||||||
# Use raw Plex API - fetch playlist directly
|
# Use raw Plex API - fetch playlist directly
|
||||||
try:
|
try:
|
||||||
raw_playlist = plex_client.server.playlist(playlist_name)
|
raw_playlist = media_server_engine.client('plex').server.playlist(playlist_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
return jsonify({"success": False, "error": "Playlist not found on server"}), 404
|
return jsonify({"success": False, "error": "Playlist not found on server"}), 404
|
||||||
|
|
||||||
|
|
@ -18376,7 +18376,7 @@ def server_playlist_replace_track(playlist_id):
|
||||||
replaced = False
|
replaced = False
|
||||||
for item in raw_playlist.items():
|
for item in raw_playlist.items():
|
||||||
if str(item.ratingKey) == str(old_track_id) and not replaced:
|
if str(item.ratingKey) == str(old_track_id) and not replaced:
|
||||||
new_item = plex_client.server.fetchItem(int(new_track_id))
|
new_item = media_server_engine.client('plex').server.fetchItem(int(new_track_id))
|
||||||
if new_item:
|
if new_item:
|
||||||
new_tracks.append(new_item)
|
new_tracks.append(new_item)
|
||||||
replaced = True
|
replaced = True
|
||||||
|
|
@ -18389,13 +18389,13 @@ def server_playlist_replace_track(playlist_id):
|
||||||
# Delete old and recreate directly (avoid update_playlist's backup logic)
|
# Delete old and recreate directly (avoid update_playlist's backup logic)
|
||||||
raw_playlist.delete()
|
raw_playlist.delete()
|
||||||
from plexapi.playlist import Playlist
|
from plexapi.playlist import Playlist
|
||||||
new_pl = Playlist.create(plex_client.server, playlist_name, items=new_tracks)
|
new_pl = Playlist.create(media_server_engine.client('plex').server, playlist_name, items=new_tracks)
|
||||||
return jsonify({"success": True, "message": "Track replaced", "new_playlist_id": str(new_pl.ratingKey)})
|
return jsonify({"success": True, "message": "Track replaced", "new_playlist_id": str(new_pl.ratingKey)})
|
||||||
else:
|
else:
|
||||||
return jsonify({"success": False, "error": "Old track not found in playlist"}), 404
|
return jsonify({"success": False, "error": "Old track not found in playlist"}), 404
|
||||||
|
|
||||||
elif active_server == 'jellyfin' and jellyfin_client:
|
elif active_server == 'jellyfin' and media_server_engine.client('jellyfin'):
|
||||||
current_tracks = jellyfin_client.get_playlist_tracks(playlist_id)
|
current_tracks = media_server_engine.client('jellyfin').get_playlist_tracks(playlist_id)
|
||||||
new_track_ids = []
|
new_track_ids = []
|
||||||
replaced = False
|
replaced = False
|
||||||
for t in (current_tracks or []):
|
for t in (current_tracks or []):
|
||||||
|
|
@ -18408,12 +18408,12 @@ def server_playlist_replace_track(playlist_id):
|
||||||
|
|
||||||
if replaced:
|
if replaced:
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_track_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_track_ids]
|
||||||
jellyfin_client.update_playlist(playlist_name, new_track_objs)
|
media_server_engine.client('jellyfin').update_playlist(playlist_name, new_track_objs)
|
||||||
return jsonify({"success": True, "message": "Track replaced"})
|
return jsonify({"success": True, "message": "Track replaced"})
|
||||||
return jsonify({"success": False, "error": "Old track not found"}), 404
|
return jsonify({"success": False, "error": "Old track not found"}), 404
|
||||||
|
|
||||||
elif active_server == 'navidrome' and navidrome_client:
|
elif active_server == 'navidrome' and media_server_engine.client('navidrome'):
|
||||||
current_tracks = navidrome_client.get_playlist_tracks(playlist_id)
|
current_tracks = media_server_engine.client('navidrome').get_playlist_tracks(playlist_id)
|
||||||
new_track_ids = []
|
new_track_ids = []
|
||||||
replaced = False
|
replaced = False
|
||||||
for t in (current_tracks or []):
|
for t in (current_tracks or []):
|
||||||
|
|
@ -18426,7 +18426,7 @@ def server_playlist_replace_track(playlist_id):
|
||||||
|
|
||||||
if replaced:
|
if replaced:
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_track_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_track_ids]
|
||||||
navidrome_client.create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
media_server_engine.client('navidrome').create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
||||||
return jsonify({"success": True, "message": "Track replaced"})
|
return jsonify({"success": True, "message": "Track replaced"})
|
||||||
return jsonify({"success": False, "error": "Old track not found"}), 404
|
return jsonify({"success": False, "error": "Old track not found"}), 404
|
||||||
|
|
||||||
|
|
@ -18452,12 +18452,12 @@ def server_playlist_add_track(playlist_id):
|
||||||
|
|
||||||
active_server = config_manager.get_active_media_server()
|
active_server = config_manager.get_active_media_server()
|
||||||
|
|
||||||
if active_server == 'plex' and plex_client:
|
if active_server == 'plex' and media_server_engine.client('plex'):
|
||||||
try:
|
try:
|
||||||
raw_playlist = plex_client.server.playlist(playlist_name)
|
raw_playlist = media_server_engine.client('plex').server.playlist(playlist_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
return jsonify({"success": False, "error": "Playlist not found"}), 404
|
return jsonify({"success": False, "error": "Playlist not found"}), 404
|
||||||
new_item = plex_client.server.fetchItem(int(track_id))
|
new_item = media_server_engine.client('plex').server.fetchItem(int(track_id))
|
||||||
if not new_item:
|
if not new_item:
|
||||||
return jsonify({"success": False, "error": "Track not found on server"}), 404
|
return jsonify({"success": False, "error": "Track not found on server"}), 404
|
||||||
|
|
||||||
|
|
@ -18484,22 +18484,22 @@ def server_playlist_add_track(playlist_id):
|
||||||
logger.info(f"[ServerPlaylist] Added track to playlist, playlist ID: {new_id}")
|
logger.info(f"[ServerPlaylist] Added track to playlist, playlist ID: {new_id}")
|
||||||
return jsonify({"success": True, "message": "Track added", "new_playlist_id": new_id})
|
return jsonify({"success": True, "message": "Track added", "new_playlist_id": new_id})
|
||||||
|
|
||||||
elif active_server == 'jellyfin' and jellyfin_client:
|
elif active_server == 'jellyfin' and media_server_engine.client('jellyfin'):
|
||||||
current_tracks = jellyfin_client.get_playlist_tracks(playlist_id) or []
|
current_tracks = media_server_engine.client('jellyfin').get_playlist_tracks(playlist_id) or []
|
||||||
track_ids = [str(t.ratingKey) for t in current_tracks]
|
track_ids = [str(t.ratingKey) for t in current_tracks]
|
||||||
pos = max(0, min(int(position), len(track_ids))) if position is not None else len(track_ids)
|
pos = max(0, min(int(position), len(track_ids))) if position is not None else len(track_ids)
|
||||||
track_ids.insert(pos, track_id)
|
track_ids.insert(pos, track_id)
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in track_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in track_ids]
|
||||||
jellyfin_client.update_playlist(playlist_name, new_track_objs)
|
media_server_engine.client('jellyfin').update_playlist(playlist_name, new_track_objs)
|
||||||
return jsonify({"success": True, "message": "Track added"})
|
return jsonify({"success": True, "message": "Track added"})
|
||||||
|
|
||||||
elif active_server == 'navidrome' and navidrome_client:
|
elif active_server == 'navidrome' and media_server_engine.client('navidrome'):
|
||||||
current_tracks = navidrome_client.get_playlist_tracks(playlist_id) or []
|
current_tracks = media_server_engine.client('navidrome').get_playlist_tracks(playlist_id) or []
|
||||||
track_ids = [str(t.ratingKey) for t in current_tracks]
|
track_ids = [str(t.ratingKey) for t in current_tracks]
|
||||||
pos = max(0, min(int(position), len(track_ids))) if position is not None else len(track_ids)
|
pos = max(0, min(int(position), len(track_ids))) if position is not None else len(track_ids)
|
||||||
track_ids.insert(pos, track_id)
|
track_ids.insert(pos, track_id)
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in track_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in track_ids]
|
||||||
navidrome_client.create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
media_server_engine.client('navidrome').create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
||||||
return jsonify({"success": True, "message": "Track added"})
|
return jsonify({"success": True, "message": "Track added"})
|
||||||
|
|
||||||
return jsonify({"success": False, "error": f"Unsupported server: {active_server}"}), 400
|
return jsonify({"success": False, "error": f"Unsupported server: {active_server}"}), 400
|
||||||
|
|
@ -18523,9 +18523,9 @@ def server_playlist_remove_track(playlist_id):
|
||||||
|
|
||||||
active_server = config_manager.get_active_media_server()
|
active_server = config_manager.get_active_media_server()
|
||||||
|
|
||||||
if active_server == 'plex' and plex_client:
|
if active_server == 'plex' and media_server_engine.client('plex'):
|
||||||
try:
|
try:
|
||||||
raw_playlist = plex_client.server.playlist(playlist_name)
|
raw_playlist = media_server_engine.client('plex').server.playlist(playlist_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
return jsonify({"success": False, "error": "Playlist not found"}), 404
|
return jsonify({"success": False, "error": "Playlist not found"}), 404
|
||||||
|
|
||||||
|
|
@ -18537,26 +18537,26 @@ def server_playlist_remove_track(playlist_id):
|
||||||
raw_playlist.delete()
|
raw_playlist.delete()
|
||||||
if new_items:
|
if new_items:
|
||||||
from plexapi.playlist import Playlist
|
from plexapi.playlist import Playlist
|
||||||
new_pl = Playlist.create(plex_client.server, playlist_name, items=new_items)
|
new_pl = Playlist.create(media_server_engine.client('plex').server, playlist_name, items=new_items)
|
||||||
return jsonify({"success": True, "message": "Track removed", "new_playlist_id": str(new_pl.ratingKey)})
|
return jsonify({"success": True, "message": "Track removed", "new_playlist_id": str(new_pl.ratingKey)})
|
||||||
return jsonify({"success": True, "message": "Track removed (playlist now empty)"})
|
return jsonify({"success": True, "message": "Track removed (playlist now empty)"})
|
||||||
|
|
||||||
elif active_server == 'jellyfin' and jellyfin_client:
|
elif active_server == 'jellyfin' and media_server_engine.client('jellyfin'):
|
||||||
current_tracks = jellyfin_client.get_playlist_tracks(playlist_id) or []
|
current_tracks = media_server_engine.client('jellyfin').get_playlist_tracks(playlist_id) or []
|
||||||
new_ids = [str(t.ratingKey) for t in current_tracks if str(t.ratingKey) != str(remove_track_id)]
|
new_ids = [str(t.ratingKey) for t in current_tracks if str(t.ratingKey) != str(remove_track_id)]
|
||||||
if len(new_ids) == len(current_tracks):
|
if len(new_ids) == len(current_tracks):
|
||||||
return jsonify({"success": False, "error": "Track not found in playlist"}), 404
|
return jsonify({"success": False, "error": "Track not found in playlist"}), 404
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_ids]
|
||||||
jellyfin_client.update_playlist(playlist_name, new_track_objs)
|
media_server_engine.client('jellyfin').update_playlist(playlist_name, new_track_objs)
|
||||||
return jsonify({"success": True, "message": "Track removed"})
|
return jsonify({"success": True, "message": "Track removed"})
|
||||||
|
|
||||||
elif active_server == 'navidrome' and navidrome_client:
|
elif active_server == 'navidrome' and media_server_engine.client('navidrome'):
|
||||||
current_tracks = navidrome_client.get_playlist_tracks(playlist_id) or []
|
current_tracks = media_server_engine.client('navidrome').get_playlist_tracks(playlist_id) or []
|
||||||
new_ids = [str(t.ratingKey) for t in current_tracks if str(t.ratingKey) != str(remove_track_id)]
|
new_ids = [str(t.ratingKey) for t in current_tracks if str(t.ratingKey) != str(remove_track_id)]
|
||||||
if len(new_ids) == len(current_tracks):
|
if len(new_ids) == len(current_tracks):
|
||||||
return jsonify({"success": False, "error": "Track not found in playlist"}), 404
|
return jsonify({"success": False, "error": "Track not found in playlist"}), 404
|
||||||
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_ids]
|
new_track_objs = [type('T', (), {'ratingKey': tid, 'title': ''})() for tid in new_ids]
|
||||||
navidrome_client.create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
media_server_engine.client('navidrome').create_playlist(playlist_name, new_track_objs, playlist_id=playlist_id)
|
||||||
return jsonify({"success": True, "message": "Track removed"})
|
return jsonify({"success": True, "message": "Track removed"})
|
||||||
|
|
||||||
return jsonify({"success": False, "error": f"Unsupported server: {active_server}"}), 400
|
return jsonify({"success": False, "error": f"Unsupported server: {active_server}"}), 400
|
||||||
|
|
@ -18580,9 +18580,9 @@ def library_search_tracks():
|
||||||
# Build thumb URL resolver for this server
|
# Build thumb URL resolver for this server
|
||||||
_art_prefix = ''
|
_art_prefix = ''
|
||||||
_art_suffix = ''
|
_art_suffix = ''
|
||||||
if active_server == 'plex' and plex_client and plex_client.server:
|
if active_server == 'plex' and media_server_engine.client('plex') and media_server_engine.client('plex').server:
|
||||||
_ab = getattr(plex_client.server, '_baseurl', '') or ''
|
_ab = getattr(media_server_engine.client('plex').server, '_baseurl', '') or ''
|
||||||
_at = getattr(plex_client.server, '_token', '') or ''
|
_at = getattr(media_server_engine.client('plex').server, '_token', '') or ''
|
||||||
if not _ab:
|
if not _ab:
|
||||||
_pc = config_manager.get_plex_config()
|
_pc = config_manager.get_plex_config()
|
||||||
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
_ab = (_pc.get('base_url', '') or '').rstrip('/')
|
||||||
|
|
@ -23399,12 +23399,12 @@ def test_database_access():
|
||||||
|
|
||||||
# Test media clients
|
# Test media clients
|
||||||
logger.info(" Media clients status:")
|
logger.info(" Media clients status:")
|
||||||
logger.info(f" plex_client: {plex_client is not None}")
|
logger.info(f" media_server_engine.client('plex'): {media_server_engine.client('plex') is not None}")
|
||||||
if plex_client:
|
if media_server_engine.client('plex'):
|
||||||
logger.info(f" plex_client.is_connected(): {plex_client.is_connected()}")
|
logger.info(f" media_server_engine.client('plex').is_connected(): {media_server_engine.client('plex').is_connected()}")
|
||||||
logger.info(f" jellyfin_client: {jellyfin_client is not None}")
|
logger.info(f" media_server_engine.client('jellyfin'): {media_server_engine.client('jellyfin') is not None}")
|
||||||
if jellyfin_client:
|
if media_server_engine.client('jellyfin'):
|
||||||
logger.info(f" jellyfin_client.is_connected(): {jellyfin_client.is_connected()}")
|
logger.info(f" media_server_engine.client('jellyfin').is_connected(): {media_server_engine.client('jellyfin').is_connected()}")
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
@ -23413,8 +23413,8 @@ def test_database_access():
|
||||||
"database_initialized": db is not None,
|
"database_initialized": db is not None,
|
||||||
"database_stats": stats,
|
"database_stats": stats,
|
||||||
"active_server": active_server,
|
"active_server": active_server,
|
||||||
"plex_connected": plex_client.is_connected() if plex_client else False,
|
"plex_connected": media_server_engine.client('plex').is_connected() if media_server_engine.client('plex') else False,
|
||||||
"jellyfin_connected": jellyfin_client.is_connected() if jellyfin_client else False,
|
"jellyfin_connected": media_server_engine.client('jellyfin').is_connected() if media_server_engine.client('jellyfin') else False,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -29133,17 +29133,17 @@ def start_metadata_update():
|
||||||
|
|
||||||
# Get appropriate media client - Support all three servers
|
# Get appropriate media client - Support all three servers
|
||||||
if active_server == "jellyfin":
|
if active_server == "jellyfin":
|
||||||
media_client = jellyfin_client
|
media_client = media_server_engine.client('jellyfin')
|
||||||
if not media_client:
|
if not media_client:
|
||||||
add_activity_item("", "Metadata Update", "Jellyfin client not available", "Now")
|
add_activity_item("", "Metadata Update", "Jellyfin client not available", "Now")
|
||||||
return jsonify({"success": False, "error": "Jellyfin client not available"}), 400
|
return jsonify({"success": False, "error": "Jellyfin client not available"}), 400
|
||||||
elif active_server == "navidrome":
|
elif active_server == "navidrome":
|
||||||
media_client = navidrome_client
|
media_client = media_server_engine.client('navidrome')
|
||||||
if not media_client:
|
if not media_client:
|
||||||
add_activity_item("", "Metadata Update", "Navidrome client not available", "Now")
|
add_activity_item("", "Metadata Update", "Navidrome client not available", "Now")
|
||||||
return jsonify({"success": False, "error": "Navidrome client not available"}), 400
|
return jsonify({"success": False, "error": "Navidrome client not available"}), 400
|
||||||
else: # plex
|
else: # plex
|
||||||
media_client = plex_client
|
media_client = media_server_engine.client('plex')
|
||||||
if not media_client:
|
if not media_client:
|
||||||
add_activity_item("", "Metadata Update", "Plex client not available", "Now")
|
add_activity_item("", "Metadata Update", "Plex client not available", "Now")
|
||||||
return jsonify({"success": False, "error": "Plex client not available"}), 400
|
return jsonify({"success": False, "error": "Plex client not available"}), 400
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue