diff --git a/core/jellyfin_client.py b/core/jellyfin_client.py index e6f1a887..b7db09da 100644 --- a/core/jellyfin_client.py +++ b/core/jellyfin_client.py @@ -1075,4 +1075,84 @@ class JellyfinClient: except Exception as e: logger.error(f"Error updating Jellyfin playlist '{playlist_name}': {e}") + return False + + def trigger_library_scan(self, library_name: str = "Music") -> bool: + """Trigger Jellyfin library scan for the specified library""" + if not self.ensure_connection(): + return False + + try: + # Get library info to find the correct library ID + libraries_response = self._make_request(f'/Users/{self.user_id}/Views') + if not libraries_response: + logger.error("Failed to get library list for scan") + return False + + target_library_id = None + for library in libraries_response.get('Items', []): + if (library.get('CollectionType') == 'music' and + library_name.lower() in library.get('Name', '').lower()): + target_library_id = library['Id'] + break + + # Default to music_library_id if no specific library found + if not target_library_id: + target_library_id = self.music_library_id + + if not target_library_id: + logger.error(f"No library found matching '{library_name}'") + return False + + # Trigger the scan using POST request + import requests + url = f"{self.base_url}/Items/{target_library_id}/Refresh" + headers = { + 'X-Emby-Token': self.api_key, + 'Content-Type': 'application/json' + } + params = { + 'Recursive': True, + 'ImageRefreshMode': 'ValidationOnly', # Don't refresh images, just metadata + 'MetadataRefreshMode': 'ValidationOnly' + } + + response = requests.post(url, headers=headers, params=params, timeout=10) + response.raise_for_status() + + logger.info(f"🎵 Triggered Jellyfin library scan for '{library_name}'") + return True + + except Exception as e: + logger.error(f"Failed to trigger Jellyfin library scan for '{library_name}': {e}") + return False + + def is_library_scanning(self, library_name: str = "Music") -> bool: + """Check if Jellyfin library is currently scanning""" + if not self.ensure_connection(): + logger.debug("🔍 DEBUG: Not connected to Jellyfin, cannot check scan status") + return False + + try: + # Check scheduled tasks for library scan activities + response = self._make_request('/ScheduledTasks') + if not response: + logger.debug("🔍 DEBUG: Could not get scheduled tasks") + return False + + for task in response: + task_name = task.get('Name', '').lower() + task_state = task.get('State', 'Idle') + + # Look for library scan related tasks that are running + if ('scan' in task_name or 'refresh' in task_name or 'library' in task_name): + if task_state in ['Running', 'Cancelling']: + logger.debug(f"🔍 DEBUG: Found running scan task: {task.get('Name')} (State: {task_state})") + return True + + logger.debug("🔍 DEBUG: No active scan tasks detected") + return False + + except Exception as e: + logger.debug(f"Error checking if Jellyfin library is scanning: {e}") return False \ No newline at end of file diff --git a/core/media_scan_manager.py b/core/media_scan_manager.py new file mode 100644 index 00000000..bc8f4eb0 --- /dev/null +++ b/core/media_scan_manager.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 + +import threading +import time +from utils.logging_config import get_logger + +logger = get_logger("media_scan_manager") + +class MediaScanManager: + """ + Smart media library scan manager with debouncing and scan-aware follow-up logic. + Supports both Plex and Jellyfin servers based on active configuration. + + Features: + - Debounces multiple scan requests to prevent spam + - Tracks downloads that happen during active scans + - Automatically triggers follow-up scans when needed + - Thread-safe operation + - Works with both Plex and Jellyfin + """ + + def __init__(self, delay_seconds: int = 60): + """ + Initialize the scan manager. + + Args: + delay_seconds: Debounce delay in seconds (default 60s) + """ + self.delay = delay_seconds + self._timer = None + self._scan_in_progress = False + self._downloads_during_scan = False + self._lock = threading.Lock() + self._scan_completion_callbacks = [] # List of callback functions to call when scan completes + self._scan_start_time = None # Track when scan started for timeout + self._max_scan_time = 1800 # Maximum scan time in seconds (30 minutes) + + # New periodic update system + self._periodic_update_timer = None # Timer for 5-minute periodic updates + self._periodic_update_interval = 300 # 5 minutes in seconds + self._is_doing_periodic_updates = False # Track if we're in periodic update mode + + logger.info(f"MediaScanManager initialized with {delay_seconds}s debounce delay") + + def _get_active_media_client(self): + """Get the active media client based on config settings""" + try: + from config.settings import config_manager + active_server = config_manager.get_active_media_server() + + # Try to get client instances from app + try: + from PyQt6.QtWidgets import QApplication + app = QApplication.instance() + + # Try to find the main window from top-level widgets + main_window = None + for widget in app.topLevelWidgets(): + if hasattr(widget, 'plex_client') and hasattr(widget, 'jellyfin_client'): + main_window = widget + break + + if main_window: + if active_server == "jellyfin": + client = getattr(main_window, 'jellyfin_client', None) + if client and client.is_connected(): + return client, "jellyfin" + else: + logger.warning("Jellyfin client not connected, falling back to Plex") + + # Default to Plex or fallback + client = getattr(main_window, 'plex_client', None) + if client and client.is_connected(): + return client, "plex" + else: + logger.debug(f"Plex client not connected or not found") + + except Exception as e: + logger.debug(f"Could not access clients from main window: {e}") + + logger.error("No active media client available") + return None, None + except Exception as e: + logger.error(f"Error determining active media server: {e}") + return None, None + + def request_scan(self, reason: str = "Download completed"): + """ + Request a library scan with smart debouncing logic. + + Args: + reason: Optional reason for the scan request (for logging) + """ + logger.info(f"DEBUG: Media scan requested - reason: {reason}") + with self._lock: + if self._scan_in_progress: + # Server is currently scanning - mark that we need another scan later + self._downloads_during_scan = True + logger.info(f"📡 Media scan in progress - queueing follow-up scan ({reason})") + return + + # Cancel any existing timer and start a new one + if self._timer: + self._timer.cancel() + logger.debug(f"⏳ Resetting scan timer ({reason})") + else: + logger.info(f"⏳ Media scan queued - will execute in {self.delay}s ({reason})") + + # Start the debounce timer + self._timer = threading.Timer(self.delay, self._execute_scan) + self._timer.start() + + def add_scan_completion_callback(self, callback): + """ + Add a callback function to be called when scan completes. + + Args: + callback: Function to call when scan completes (no arguments) + """ + with self._lock: + if callback not in self._scan_completion_callbacks: + self._scan_completion_callbacks.append(callback) + logger.info(f"DEBUG: Added scan completion callback: {callback.__name__}") + logger.info(f"DEBUG: Total callbacks registered: {len(self._scan_completion_callbacks)}") + + def remove_scan_completion_callback(self, callback): + """ + Remove a previously registered callback. + + Args: + callback: Function to remove from callbacks + """ + with self._lock: + if callback in self._scan_completion_callbacks: + self._scan_completion_callbacks.remove(callback) + logger.debug(f"Removed scan completion callback: {callback.__name__}") + + def _execute_scan(self): + """Execute the actual media library scan""" + with self._lock: + if self._scan_in_progress: + logger.warning("Scan already in progress - skipping duplicate execution") + return + + self._scan_in_progress = True + self._downloads_during_scan = False + self._timer = None + self._scan_start_time = time.time() + + # Get the active media client + media_client, server_type = self._get_active_media_client() + if not media_client: + logger.error("❌ No active media client available for library scan") + self._reset_scan_state() + return + + logger.info(f"🎵 Starting {server_type.upper()} library scan...") + + try: + success = media_client.trigger_library_scan() + + if success: + logger.info(f"✅ {server_type.upper()} library scan initiated successfully") + # Start new periodic update system instead of completion detection + self._start_periodic_updates() + else: + logger.error(f"❌ Failed to initiate {server_type.upper()} library scan") + self._reset_scan_state() + + except Exception as e: + logger.error(f"Exception during {server_type.upper()} library scan: {e}") + self._reset_scan_state() + + def _start_periodic_updates(self): + """Start periodic database updates while media server is scanning""" + try: + with self._lock: + if self._is_doing_periodic_updates: + logger.debug("Periodic updates already in progress") + return + + self._is_doing_periodic_updates = True + + logger.info(f"🕒 Starting periodic database updates - will check/update every {self._periodic_update_interval//60} minutes") + + # Schedule first periodic update after 5 minutes + self._periodic_update_timer = threading.Timer(self._periodic_update_interval, self._do_periodic_update) + self._periodic_update_timer.start() + + except Exception as e: + logger.error(f"Error starting periodic updates: {e}") + self._reset_scan_state() + + def _do_periodic_update(self): + """Execute periodic database update and check if scanning continues""" + try: + with self._lock: + if not self._scan_in_progress: + logger.debug("Scan no longer in progress, stopping periodic updates") + return + + # Check for timeout + if self._scan_start_time and (time.time() - self._scan_start_time) > self._max_scan_time: + logger.warning(f"Media scan timeout reached ({self._max_scan_time}s), stopping periodic updates") + self._stop_periodic_updates() + return + + # Get the active media client + media_client, server_type = self._get_active_media_client() + if not media_client: + logger.warning("No active media client available for scan status check") + self._stop_periodic_updates() + return + + # Check if media server is still scanning + is_scanning = media_client.is_library_scanning("Music") + elapsed_time = time.time() - self._scan_start_time if self._scan_start_time else 0 + + logger.info(f"🕒 PERIODIC UPDATE: After {elapsed_time//60:.0f} minutes - {server_type.upper()} scanning: {is_scanning}") + + if is_scanning: + # Still scanning - trigger database update and continue periodic updates + logger.info(f"🔄 {server_type.upper()} still scanning - triggering database update") + self._call_completion_callbacks() + + # Schedule next periodic update + logger.info(f"🕒 Scheduling next periodic update in {self._periodic_update_interval//60} minutes") + self._periodic_update_timer = threading.Timer(self._periodic_update_interval, self._do_periodic_update) + self._periodic_update_timer.start() + else: + # Scanning stopped - final update and cleanup + logger.info(f"✅ {server_type.upper()} scanning completed - doing final database update") + self._call_completion_callbacks() + self._stop_periodic_updates() + + except Exception as e: + logger.error(f"Error during periodic update: {e}") + self._stop_periodic_updates() + + def _stop_periodic_updates(self): + """Stop periodic updates and clean up""" + try: + with self._lock: + self._is_doing_periodic_updates = False + + if self._periodic_update_timer: + self._periodic_update_timer.cancel() + self._periodic_update_timer = None + + logger.info("🕒 Stopped periodic database updates") + self._scan_completed() + + except Exception as e: + logger.error(f"Error stopping periodic updates: {e}") + + def _scan_completed(self): + """Called when we assume the scan has completed""" + with self._lock: + was_in_progress = self._scan_in_progress + downloads_during_scan = self._downloads_during_scan + + # Reset scan state + self._scan_in_progress = False + + if not was_in_progress: + logger.debug("Scan completion callback called but scan was not in progress") + return + + logger.info("📡 Media library scan completed") + + # Call registered completion callbacks + self._call_completion_callbacks() + + # Check if we need a follow-up scan + if downloads_during_scan: + logger.info("🔄 Downloads occurred during scan - triggering follow-up scan") + self.request_scan("Follow-up scan for downloads during previous scan") + else: + logger.info("✅ No downloads during scan - scan cycle complete") + + def _call_completion_callbacks(self): + """Call all registered scan completion callbacks""" + with self._lock: + callbacks = self._scan_completion_callbacks.copy() # Copy to avoid lock issues + + logger.info(f"DEBUG: Calling {len(callbacks)} scan completion callbacks") + for callback in callbacks: + try: + logger.info(f"DEBUG: Executing callback: {callback.__name__}") + callback() + logger.info(f"DEBUG: Callback {callback.__name__} completed successfully") + except Exception as e: + logger.error(f"Error in scan completion callback {callback.__name__}: {e}") + + def _reset_scan_state(self): + """Reset scan state after an error""" + with self._lock: + self._scan_in_progress = False + self._scan_start_time = None + + # Cancel periodic updates if running + if self._periodic_update_timer: + self._periodic_update_timer.cancel() + self._periodic_update_timer = None + self._is_doing_periodic_updates = False + + def force_scan(self): + """ + Force an immediate scan, bypassing debouncing. + Use sparingly - mainly for manual/administrative triggers. + """ + with self._lock: + if self._timer: + self._timer.cancel() + self._timer = None + + if self._scan_in_progress: + logger.warning("Force scan requested but scan already in progress") + return + + logger.info("🚀 Force scan requested - executing immediately") + self._execute_scan() + + def get_status(self) -> dict: + """Get current status of the scan manager""" + with self._lock: + return { + 'scan_in_progress': self._scan_in_progress, + 'downloads_during_scan': self._downloads_during_scan, + 'timer_active': self._timer is not None, + 'delay_seconds': self.delay + } + + def shutdown(self): + """Clean shutdown - cancel any pending timers""" + with self._lock: + if self._timer: + self._timer.cancel() + self._timer = None + + if self._periodic_update_timer: + self._periodic_update_timer.cancel() + self._periodic_update_timer = None + + self._is_doing_periodic_updates = False + logger.info("MediaScanManager shutdown - cancelled all pending timers") \ No newline at end of file diff --git a/database/music_database.py b/database/music_database.py index df37655f..ae41e90e 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -557,7 +557,26 @@ class MusicDatabase: artist_id = str(artist_obj.ratingKey) name = artist_obj.title thumb_url = getattr(artist_obj, 'thumb', None) - summary = getattr(artist_obj, 'summary', None) + + # Only preserve timestamps and flags from summary, not full biography + full_summary = getattr(artist_obj, 'summary', None) or '' + summary = None + if full_summary: + # Extract only our tracking markers (timestamps and ignore flags) + import re + markers = [] + + # Extract timestamp marker + timestamp_match = re.search(r'-updatedAt\d{4}-\d{2}-\d{2}', full_summary) + if timestamp_match: + markers.append(timestamp_match.group(0)) + + # Extract ignore flag + if '-IgnoreUpdate' in full_summary: + markers.append('-IgnoreUpdate') + + # Only store markers, not full biography + summary = '\n\n'.join(markers) if markers else None # Get genres (handle both Plex and Jellyfin formats) genres = [] diff --git a/database/music_library.db-shm b/database/music_library.db-shm new file mode 100644 index 00000000..6512b3bb Binary files /dev/null and b/database/music_library.db-shm differ diff --git a/database/music_library.db-wal b/database/music_library.db-wal new file mode 100644 index 00000000..84d80a9c Binary files /dev/null and b/database/music_library.db-wal differ diff --git a/services/sync_service.py b/services/sync_service.py index 0a66bc7a..b5f0c5c6 100644 --- a/services/sync_service.py +++ b/services/sync_service.py @@ -311,12 +311,18 @@ class PlaylistSyncService: return actual_track, confidence else: # For Plex, use the original fetchItem approach - actual_plex_track = media_client.server.fetchItem(db_track.id) - if actual_plex_track and hasattr(actual_plex_track, 'ratingKey'): - logger.debug(f"✔️ Successfully fetched actual Plex track for '{db_track.title}' (ratingKey: {actual_plex_track.ratingKey})") - return actual_plex_track, confidence - else: - logger.warning(f"❌ Fetched Plex track for '{db_track.title}' lacks ratingKey attribute") + # Validate that the track ID is numeric (Plex requirement) + try: + track_id = int(db_track.id) + actual_plex_track = media_client.server.fetchItem(track_id) + if actual_plex_track and hasattr(actual_plex_track, 'ratingKey'): + logger.debug(f"✔️ Successfully fetched actual Plex track for '{db_track.title}' (ratingKey: {actual_plex_track.ratingKey})") + return actual_plex_track, confidence + else: + logger.warning(f"❌ Fetched Plex track for '{db_track.title}' lacks ratingKey attribute") + except ValueError: + logger.warning(f"❌ Invalid Plex track ID format for '{db_track.title}' (ID: {db_track.id}) - skipping this track") + continue except Exception as fetch_error: logger.error(f"❌ Failed to fetch actual {server_type} track for '{db_track.title}' (ID: {db_track.id}): {fetch_error}") diff --git a/ui/components/database_updater_widget.py b/ui/components/database_updater_widget.py index 59eff0be..c6598694 100644 --- a/ui/components/database_updater_widget.py +++ b/ui/components/database_updater_widget.py @@ -33,8 +33,15 @@ class DatabaseUpdaterWidget(QFrame): header_label.setFont(QFont("Arial", 14, QFont.Weight.Bold)) header_label.setStyleSheet("color: #ffffff;") - # Info label - info_label = QLabel("Syncs your Plex music library into the local database for faster searches and analytics") + # Info label - dynamic based on active server + try: + from config.settings import config_manager + active_server = config_manager.get_active_media_server() + server_name = "Jellyfin" if active_server == "jellyfin" else "Plex" + except: + server_name = "Plex" # Fallback + + info_label = QLabel(f"Syncs your {server_name} music library into the local database for faster searches and analytics") info_label.setFont(QFont("Arial", 9)) info_label.setStyleSheet("color: #b3b3b3; margin-bottom: 5px;") info_label.setWordWrap(True) diff --git a/ui/pages/artists.py b/ui/pages/artists.py index 1a4d3a19..2a49ed71 100644 --- a/ui/pages/artists.py +++ b/ui/pages/artists.py @@ -3208,7 +3208,7 @@ class ArtistsPage(QWidget): self.download_status_timer.start(2000) # Poll every 2 seconds (consistent with sync.py) self.download_status_pool = QThreadPool() - # Initialize Plex scan manager (will be set when clients are connected) + # Initialize unified media scan manager (will be set when clients are connected) self.scan_manager = None self.download_status_pool.setMaxThreadCount(1) # One worker at a time to avoid conflicts self._is_status_update_running = False @@ -3224,16 +3224,24 @@ class ArtistsPage(QWidget): """Set the toast manager for showing notifications""" self.toast_manager = toast_manager - def _on_plex_scan_completed(self): - """Callback triggered when Plex scan completes - start automatic incremental database update""" + def _on_media_scan_completed(self): + """Callback triggered when media scan completes - start automatic incremental database update""" try: # Import here to avoid circular imports from database import get_database from core.database_update_worker import DatabaseUpdateWorker + from config.settings import config_manager + + # Get the active media client + active_server = config_manager.get_active_media_server() + if active_server == "jellyfin": + media_client = getattr(self, 'jellyfin_client', None) + else: + media_client = getattr(self, 'plex_client', None) # Check if we should run incremental update - if not self.plex_client or not self.plex_client.is_connected(): - logger.debug("Plex not connected - skipping automatic database update") + if not media_client or not media_client.is_connected(): + logger.debug(f"{active_server.upper()} not connected - skipping automatic database update") return # Check if database has a previous full refresh @@ -3256,11 +3264,11 @@ class ArtistsPage(QWidget): return # All conditions met - start incremental update - logger.info("🎵 Starting automatic incremental database update after Plex scan") + logger.info(f"🎵 Starting automatic incremental database update after {active_server.upper()} scan") self._start_automatic_incremental_update() except Exception as e: - logger.error(f"Error in Plex scan completion callback: {e}") + logger.error(f"Error in media scan completion callback: {e}") def _start_automatic_incremental_update(self): """Start the automatic incremental database update""" @@ -3333,12 +3341,15 @@ class ArtistsPage(QWidget): print(f"✅ Set soulseek_client download path for ArtistsPage to: {download_path}") # --- END FIX --- - # Initialize Plex scan manager now that clients are available - if self.plex_client: - self.scan_manager = PlexScanManager(self.plex_client, delay_seconds=60) - # Add automatic incremental database update after Plex scan completion - self.scan_manager.add_scan_completion_callback(self._on_plex_scan_completed) - print("✅ PlexScanManager initialized for ArtistsPage") + # Initialize unified media scan manager now that clients are available + try: + from core.media_scan_manager import MediaScanManager + self.scan_manager = MediaScanManager(delay_seconds=60) + # Add automatic incremental database update after scan completion + self.scan_manager.add_scan_completion_callback(self._on_media_scan_completed) + print("✅ MediaScanManager initialized for ArtistsPage") + except Exception as e: + print(f"Failed to initialize MediaScanManager: {e}") except Exception as e: print(f"Failed to initialize clients: {e}") diff --git a/ui/pages/dashboard.py b/ui/pages/dashboard.py index 1fdc9646..bd6fa1f1 100644 --- a/ui/pages/dashboard.py +++ b/ui/pages/dashboard.py @@ -2703,13 +2703,16 @@ class DashboardPage(QWidget): 'downloads_page': downloads_page } - # Initialize Plex scan manager for wishlist modal integration + # Initialize unified media scan manager for wishlist modal integration self.scan_manager = None - if plex_client: - self.scan_manager = PlexScanManager(plex_client, delay_seconds=60) - # Add automatic incremental database update after Plex scan completion - self.scan_manager.add_scan_completion_callback(self._on_plex_scan_completed) - logger.info("✅ PlexScanManager initialized for Dashboard wishlist modal") + try: + from core.media_scan_manager import MediaScanManager + self.scan_manager = MediaScanManager(delay_seconds=60) + # Add automatic incremental database update after scan completion + self.scan_manager.add_scan_completion_callback(self._on_media_scan_completed) + logger.info("✅ MediaScanManager initialized for Dashboard wishlist modal") + except Exception as e: + logger.error(f"Failed to initialize MediaScanManager: {e}") def set_page_references(self, downloads_page, sync_page): """Called from main window to provide page references for live data""" @@ -2725,17 +2728,24 @@ class DashboardPage(QWidget): """Set the toast manager for showing notifications""" self.toast_manager = toast_manager - def _on_plex_scan_completed(self): - """Callback triggered when Plex scan completes - start automatic incremental database update""" + def _on_media_scan_completed(self): + """Callback triggered when media scan completes - start automatic incremental database update""" try: # Import here to avoid circular imports from database import get_database from core.database_update_worker import DatabaseUpdateWorker + from config.settings import config_manager + + # Get the active media client + active_server = config_manager.get_active_media_server() + if active_server == "jellyfin": + media_client = self.service_clients.get('jellyfin_client') + else: + media_client = self.service_clients.get('plex_client') # Check if we should run incremental update - plex_client = self.service_clients.get('plex_client') - if not plex_client or not plex_client.is_connected(): - logger.debug("Plex not connected - skipping automatic database update") + if not media_client or not media_client.is_connected(): + logger.debug(f"{active_server.upper()} not connected - skipping automatic database update") return # Check if database has a previous full refresh @@ -2758,11 +2768,11 @@ class DashboardPage(QWidget): return # All conditions met - start incremental update - logger.info("🎵 Starting automatic incremental database update after Plex scan") + logger.info(f"🎵 Starting automatic incremental database update after {active_server.upper()} scan") self._start_automatic_incremental_update() except Exception as e: - logger.error(f"Error in Plex scan completion callback: {e}") + logger.error(f"Error in media scan completion callback: {e}") def _start_automatic_incremental_update(self): """Start the automatic incremental database update""" diff --git a/ui/pages/sync.py b/ui/pages/sync.py index 6b1b18f4..a22e0245 100644 --- a/ui/pages/sync.py +++ b/ui/pages/sync.py @@ -2716,12 +2716,16 @@ class SyncPage(QWidget): self.youtube_cards = {} # url -> YouTubePlaylistCard instance self.youtube_cards_container = None # Container for all YouTube cards - # Initialize Plex scan manager + # Initialize unified media scan manager self.scan_manager = None - if self.plex_client: - self.scan_manager = PlexScanManager(self.plex_client, delay_seconds=60) - # Add automatic incremental database update after Plex scan completion - self.scan_manager.add_scan_completion_callback(self._on_plex_scan_completed) + try: + from core.media_scan_manager import MediaScanManager + self.scan_manager = MediaScanManager(delay_seconds=60) + # Add automatic incremental database update after scan completion + self.scan_manager.add_scan_completion_callback(self._on_media_scan_completed) + logger.info("✅ MediaScanManager initialized for SyncPage") + except Exception as e: + logger.error(f"Failed to initialize MediaScanManager: {e}") self.setup_ui() @@ -2733,16 +2737,24 @@ class SyncPage(QWidget): """Set the toast manager for showing notifications""" self.toast_manager = toast_manager - def _on_plex_scan_completed(self): - """Callback triggered when Plex scan completes - start automatic incremental database update""" + def _on_media_scan_completed(self): + """Callback triggered when media scan completes - start automatic incremental database update""" try: # Import here to avoid circular imports from database import get_database from core.database_update_worker import DatabaseUpdateWorker + from config.settings import config_manager + + # Get the active media client + active_server = config_manager.get_active_media_server() + if active_server == "jellyfin": + media_client = getattr(self, 'jellyfin_client', None) + else: + media_client = getattr(self, 'plex_client', None) # Check if we should run incremental update - if not self.plex_client or not self.plex_client.is_connected(): - logger.debug("Plex not connected - skipping automatic database update") + if not media_client or not media_client.is_connected(): + logger.debug(f"{active_server.upper()} not connected - skipping automatic database update") return # Check if database has a previous full refresh @@ -2765,11 +2777,11 @@ class SyncPage(QWidget): return # All conditions met - start incremental update - logger.info("🎵 Starting automatic incremental database update after Plex scan") + logger.info(f"🎵 Starting automatic incremental database update after {active_server.upper()} scan") self._start_automatic_incremental_update() except Exception as e: - logger.error(f"Error in Plex scan completion callback: {e}") + logger.error(f"Error in media scan completion callback: {e}") def _start_automatic_incremental_update(self): """Start the automatic incremental database update"""