@@ -505,6 +505,7 @@ def get(self):
505505 # Collect all connections for parallel testing
506506 connection_candidates = []
507507 connections = []
508+ all_device_connection_uris = [] # Store ALL URIs before testing
508509 for conn in device .get ('connections' , []):
509510 connection_data = {
510511 'uri' : conn ['uri' ],
@@ -514,6 +515,7 @@ def get(self):
514515 'local' : conn .get ('local' , False )
515516 }
516517 connection_candidates .append (connection_data )
518+ all_device_connection_uris .append (conn ['uri' ]) # Store ALL URIs
517519
518520 # Test all connections in parallel using threads
519521 if connection_candidates :
@@ -545,15 +547,28 @@ def test_connection_wrapper(conn_data):
545547 connections .sort (key = lambda x : x .get ('latency' , float ('inf' )))
546548 bestConnection = connections [0 ] if connections else None
547549
548- servers . append ( {
550+ server_data = {
549551 'name' : device ['name' ],
550552 'machineIdentifier' : device ['clientIdentifier' ],
551553 'connections' : connections ,
552554 'bestConnection' : bestConnection ,
553555 'version' : device .get ('productVersion' ),
554556 'platform' : device .get ('platform' ),
555557 'device' : device .get ('device' )
556- })
558+ }
559+ servers .append (server_data )
560+
561+ # Update stored connections if this is the currently selected server
562+ selected_machine_id = settings .plex .get ('server_machine_id' )
563+ if selected_machine_id and device ['clientIdentifier' ] == selected_machine_id :
564+ # Store ALL connection URIs (not just the working ones) for round-robin fallback
565+ settings .plex .server_connections = all_device_connection_uris
566+ # Update best connection if it changed
567+ if bestConnection :
568+ settings .plex .server_url = bestConnection ['uri' ]
569+ settings .plex .server_local = bestConnection .get ('local' , False )
570+ write_config ()
571+ logger .debug (f"Auto-updated connections for server { device ['name' ]} : { len (all_device_connection_uris )} total, { len (connections )} available" )
557572
558573 return {'data' : servers }
559574
@@ -571,69 +586,99 @@ def get(self):
571586 try :
572587 decrypted_token = get_decrypted_token ()
573588 if not decrypted_token :
574- logger .warning ("No decrypted token available for Plex library fetching" )
589+ logger .debug ("No decrypted token available for Plex library fetching" )
575590 return {'data' : []}
576591
577- # Get the selected server URL
578- server_url = settings .plex .get ('server_url' )
579- if not server_url :
580- logger .warning ("No Plex server selected" )
592+ # Get all stored server connections for round-robin fallback
593+ primary_url = settings .plex .get ('server_url' )
594+ all_connections = settings .plex .get ('server_connections' , [])
595+
596+ # Build connection list: primary URL first, then others as fallback
597+ server_connections = []
598+ if primary_url :
599+ server_connections .append (primary_url )
600+ # Add other connections as fallback (skip duplicates)
601+ server_connections .extend ([url for url in all_connections if url != primary_url ])
602+ elif all_connections :
603+ server_connections = all_connections
604+ else :
605+ logger .debug ("No Plex server connections available" )
581606 return {'data' : []}
582607
583- logger .debug (f"Fetching Plex libraries from server: { sanitize_server_url ( server_url )} " )
608+ logger .debug (f"Fetching Plex libraries for server: { settings . plex . get ( 'server_name' , 'Unknown' )} " )
584609
585610 headers = {
586611 'X-Plex-Token' : decrypted_token ,
587612 'Accept' : 'application/json'
588613 }
589614
590- # Get libraries from the selected server
591- response = requests .get (
592- f"{ server_url } /library/sections" ,
593- headers = headers ,
594- timeout = 10 ,
595- verify = False
596- )
615+ # Try each connection in order until one succeeds (round-robin)
616+ sections = []
617+ successful_server_url = None
618+
619+ for idx , server_url in enumerate (server_connections , 1 ):
620+ try :
621+ logger .debug (f"Attempting to fetch libraries from connection { idx } /{ len (server_connections )} : { sanitize_server_url (server_url )} " )
622+
623+ # Get libraries from this server URL
624+ lib_response = requests .get (
625+ f"{ server_url } /library/sections" ,
626+ headers = headers ,
627+ timeout = 10 ,
628+ verify = False
629+ )
630+
631+ if lib_response .status_code in (401 , 403 ):
632+ logger .debug (f"Connection { idx } : Authentication failed ({ lib_response .status_code } )" )
633+ continue
634+ elif lib_response .status_code != 200 :
635+ logger .debug (f"Connection { idx } : HTTP { lib_response .status_code } " )
636+ continue
597637
598- if response .status_code in (401 , 403 ):
599- logger .warning (f"Plex authentication failed: { response .status_code } " )
600- return {'data' : []}
601- elif response .status_code != 200 :
602- logger .error (f"Plex API error: { response .status_code } " )
603- raise PlexConnectionError (f"Failed to get libraries: HTTP { response .status_code } " )
638+ # Parse the response
639+ lib_content_type = lib_response .headers .get ('content-type' , '' )
640+
641+ if 'application/json' in lib_content_type :
642+ data = lib_response .json ()
643+ if 'MediaContainer' in data and 'Directory' in data ['MediaContainer' ]:
644+ sections = data ['MediaContainer' ]['Directory' ]
645+ elif 'application/xml' in lib_content_type or 'text/xml' in lib_content_type :
646+ import xml .etree .ElementTree as ET
647+ root = ET .fromstring (lib_response .text )
648+ sections = []
649+ for directory in root .findall ('Directory' ):
650+ sections .append ({
651+ 'key' : directory .get ('key' ),
652+ 'title' : directory .get ('title' ),
653+ 'type' : directory .get ('type' ),
654+ 'count' : int (directory .get ('count' , 0 )),
655+ 'agent' : directory .get ('agent' , '' ),
656+ 'scanner' : directory .get ('scanner' , '' ),
657+ 'language' : directory .get ('language' , '' ),
658+ 'uuid' : directory .get ('uuid' , '' ),
659+ 'updatedAt' : int (directory .get ('updatedAt' , 0 )),
660+ 'createdAt' : int (directory .get ('createdAt' , 0 ))
661+ })
662+
663+ # If we got sections, this connection worked
664+ if sections :
665+ successful_server_url = server_url
666+ logger .debug (f"Successfully fetched libraries from connection { idx } /{ len (server_connections )} " )
667+ break
668+ else :
669+ logger .debug (f"Connection { idx } : No sections returned" )
670+
671+ except requests .exceptions .RequestException as e :
672+ logger .debug (f"Connection { idx } failed: { type (e ).__name__ } : { str (e )} " )
673+ continue
674+ except Exception as e :
675+ logger .debug (f"Connection { idx } error: { type (e ).__name__ } : { str (e )} " )
676+ continue
604677
605- response .raise_for_status ()
606-
607- # Parse the response - it could be JSON or XML depending on the server
608- content_type = response .headers .get ('content-type' , '' )
609- logger .debug (f"Plex libraries response content-type: { content_type } " )
610-
611- if 'application/json' in content_type :
612- data = response .json ()
613- logger .debug (f"Plex libraries JSON response: { data } " )
614- if 'MediaContainer' in data and 'Directory' in data ['MediaContainer' ]:
615- sections = data ['MediaContainer' ]['Directory' ]
616- else :
617- sections = []
618- elif 'application/xml' in content_type or 'text/xml' in content_type :
619- import xml .etree .ElementTree as ET
620- root = ET .fromstring (response .text )
621- sections = []
622- for directory in root .findall ('Directory' ):
623- sections .append ({
624- 'key' : directory .get ('key' ),
625- 'title' : directory .get ('title' ),
626- 'type' : directory .get ('type' ),
627- 'count' : int (directory .get ('count' , 0 )),
628- 'agent' : directory .get ('agent' , '' ),
629- 'scanner' : directory .get ('scanner' , '' ),
630- 'language' : directory .get ('language' , '' ),
631- 'uuid' : directory .get ('uuid' , '' ),
632- 'updatedAt' : int (directory .get ('updatedAt' , 0 )),
633- 'createdAt' : int (directory .get ('createdAt' , 0 ))
634- })
635- else :
636- raise PlexConnectionError (f"Unexpected response format: { content_type } " )
678+ # If no connection succeeded, return empty
679+ if not successful_server_url or not sections :
680+ logger .warning (f"Failed to fetch libraries from all { len (server_connections )} connection(s)" )
681+ return {'data' : []}
637682
638683 # Filter and format libraries for movie and show types only
639684 libraries = []
@@ -643,7 +688,7 @@ def get(self):
643688 try :
644689 section_key = section .get ('key' )
645690 count_response = requests .get (
646- f"{ server_url } /library/sections/{ section_key } /all" ,
691+ f"{ successful_server_url } /library/sections/{ section_key } /all" ,
647692 headers = {'X-Plex-Token' : decrypted_token , 'Accept' : 'application/json' },
648693 timeout = 5 ,
649694 verify = False
@@ -674,10 +719,10 @@ def get(self):
674719 'uuid' : section .get ('uuid' , '' ),
675720 'updatedAt' : int (section .get ('updatedAt' , 0 )),
676721 'createdAt' : int (section .get ('createdAt' , 0 )),
677- 'locations' : _get_library_locations (server_url , section_key , decrypted_token )
722+ 'locations' : _get_library_locations (successful_server_url , section_key , decrypted_token )
678723 })
679724
680- logger .debug (f"Filtered Plex libraries: { libraries } " )
725+ logger .debug (f"Successfully retrieved { len ( libraries ) } movie/show libraries from Plex " )
681726 return {'data' : libraries }
682727
683728 except requests .exceptions .RequestException as e :
@@ -836,6 +881,7 @@ def get(self):
836881 post_request_parser .add_argument ('name' , type = str , required = True , help = 'Server name' )
837882 post_request_parser .add_argument ('uri' , type = str , required = True , help = 'Connection URI' )
838883 post_request_parser .add_argument ('local' , type = str , required = False , default = 'false' , help = 'Is local connection' )
884+ post_request_parser .add_argument ('connections' , type = list , location = 'json' , required = False , help = 'All available connection URIs' )
839885
840886 @api_ns_plex .doc (parser = post_request_parser )
841887 def post (self ):
@@ -844,11 +890,14 @@ def post(self):
844890 name = args .get ('name' )
845891 connection_uri = args .get ('uri' )
846892 connection_local = args .get ('local' , 'false' ).lower () == 'true'
893+ connections = args .get ('connections' , [])
847894
848895 settings .plex .server_machine_id = machine_identifier
849896 settings .plex .server_name = name
850897 settings .plex .server_url = connection_uri
851898 settings .plex .server_local = connection_local
899+ # Store all connection URIs for round-robin fallback
900+ settings .plex .server_connections = connections if connections else [connection_uri ]
852901 write_config ()
853902
854903 return {
@@ -904,13 +953,12 @@ def post(self):
904953 instance_name = settings .general .get ('instance_name' , 'Bazarr' )
905954 instance_param = quote_plus (instance_name )
906955
956+ scheme = 'https' if request .is_secure else 'http'
957+ host = request .host
907958 if configured_base_url :
908- webhook_url = f"{ configured_base_url } /api/webhooks/plex?apikey={ apikey } &instance={ instance_param } "
909- logger .info (f"Using configured base URL for webhook: { configured_base_url } /api/webhooks/plex (instance: { instance_name } )" )
959+ webhook_url = f"{ scheme } :// { host } { configured_base_url } /api/webhooks/plex?apikey={ apikey } &instance={ instance_param } "
960+ logger .info (f"Using configured base URL for webhook: { scheme } :// { host } { configured_base_url } /api/webhooks/plex (instance: { instance_name } )" )
910961 else :
911- # Fall back to using the current request's host
912- scheme = 'https' if request .is_secure else 'http'
913- host = request .host
914962 webhook_url = f"{ scheme } ://{ host } /api/webhooks/plex?apikey={ apikey } &instance={ instance_param } "
915963 logger .info (f"Using request host for webhook (no base URL configured): { scheme } ://{ host } /api/webhooks/plex (instance: { instance_name } )" )
916964 logger .info ("Note: If Bazarr is behind a reverse proxy, configure Base URL in General Settings for better reliability" )
0 commit comments