multi-server source
This commit is contained in:
parent
620c78766b
commit
0c90f4f327
10 changed files with 537 additions and 46 deletions
|
|
@ -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
|
||||
346
core/media_scan_manager.py
Normal file
346
core/media_scan_manager.py
Normal file
|
|
@ -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")
|
||||
|
|
@ -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 = []
|
||||
|
|
|
|||
BIN
database/music_library.db-shm
Normal file
BIN
database/music_library.db-shm
Normal file
Binary file not shown.
BIN
database/music_library.db-wal
Normal file
BIN
database/music_library.db-wal
Normal file
Binary file not shown.
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue