web: status dash updates

This commit is contained in:
2025-10-22 20:27:30 -04:00
committed by sbcrumb
parent 413c8ad6f6
commit 68baa1766c
4 changed files with 252 additions and 65 deletions
+191 -33
View File
@@ -17,6 +17,23 @@ from api.models import (
)
# Web routes removed - handled by separate web container
# Global scan status tracking for detailed progress
scan_status = {
"scanning": False,
"scan_type": None,
"scan_mode": None,
"start_time": None,
"current_operation": None,
"tv_series_processed": 0,
"tv_series_total": 0,
"tv_series_skipped": 0,
"movies_processed": 0,
"movies_total": 0,
"movies_skipped": 0,
"current_item": None,
"last_update": None
}
# ---------------------------
# Helper Functions
@@ -565,6 +582,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
print(f"🚀 MANUAL SCAN STARTED: {scan_type} scan (mode: {scan_mode}) initiated at {local_start.strftime('%Y-%m-%d %H:%M:%S')}{tz_display}")
# Initialize scan tracking
start_scan_tracking(scan_type, scan_mode)
# Initialize counters for scan statistics
tv_series_total = 0
tv_series_skipped = 0
@@ -625,57 +645,81 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
else:
# Full series processing - scan subdirectories
import re
tv_count = 0
# Count total series first for progress tracking
tv_series_list = []
for item in scan_path.iterdir():
if (item.is_dir() and
not item.name.lower().startswith('season') and
not re.match(r'^season\s+\d+$', item.name, re.IGNORECASE) and
nfo_manager.parse_imdb_from_path(item)):
tv_series_list.append(item)
tv_series_count = len(tv_series_list)
update_scan_status("tv", tv_series_total=tv_series_count)
print(f"INFO: Found {tv_series_count} TV series to process")
tv_count = 0
for item in tv_series_list:
# Check for shutdown signal at start of each item
shutdown_event = dependencies.get("shutdown_event")
if shutdown_event and shutdown_event.is_set():
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping scan gracefully")
return
if (item.is_dir() and
not item.name.lower().startswith('season') and
not re.match(r'^season\s+\d+$', item.name, re.IGNORECASE) and
nfo_manager.parse_imdb_from_path(item)):
tv_count += 1
try:
# Determine force_scan based on scan mode
force_scan = (scan_mode == "full")
result = tv_processor.process_series(item, force_scan=force_scan)
tv_series_total += 1
if result == "skipped":
tv_series_skipped += 1
elif result == "processed":
tv_series_processed += 1
except Exception as e:
print(f"ERROR: Failed processing TV series {item}: {e}")
tv_series_total += 1
tv_count += 1
update_scan_status(current_item=item.name, tv_series_processed=tv_count)
try:
# Determine force_scan based on scan mode
force_scan = (scan_mode == "full")
result = tv_processor.process_series(item, force_scan=force_scan)
tv_series_total += 1
if result == "skipped":
tv_series_skipped += 1
elif result == "processed":
tv_series_processed += 1
except Exception as e:
print(f"ERROR: Failed processing TV series {item}: {e}")
tv_series_total += 1
# Yield control every TV series to allow other requests
if tv_count % 1 == 0:
await asyncio.sleep(0.2) # 200ms yield to process other requests
print(f"INFO: Processed {tv_count} TV series, yielding to other requests...")
# Yield control every TV series to allow other requests
if tv_count % 1 == 0:
await asyncio.sleep(0.2) # 200ms yield to process other requests
print(f"INFO: Processed {tv_count} TV series, yielding to other requests...")
# Check for shutdown signal
shutdown_event = dependencies.get("shutdown_event")
if shutdown_event and shutdown_event.is_set():
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping scan gracefully")
return
# Check for shutdown signal
shutdown_event = dependencies.get("shutdown_event")
if shutdown_event and shutdown_event.is_set():
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping scan gracefully")
return
if scan_type in ["both", "movies"] and scan_path in config.movie_paths:
print(f"INFO: Scanning movies in: {scan_path}")
movie_count = 0
update_scan_status("movies", current_item="Counting movies...")
# Count total movies first for progress tracking
movie_list = []
for item in scan_path.iterdir():
if item.is_dir() and nfo_manager.find_movie_imdb_id(item):
movie_list.append(item)
movie_total_count = len(movie_list)
update_scan_status(movies_total=movie_total_count)
print(f"INFO: Found {movie_total_count} movies to process")
movie_count = 0
for item in movie_list:
# Check for shutdown signal at start of each movie
shutdown_event = dependencies.get("shutdown_event")
if shutdown_event and shutdown_event.is_set():
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping scan gracefully")
return
if item.is_dir() and nfo_manager.find_movie_imdb_id(item):
movie_count += 1
print(f"INFO: Processing movie: {item.name}")
try:
movie_count += 1
update_scan_status(current_item=item.name, movies_processed=movie_count)
print(f"INFO: Processing movie: {item.name}")
try:
# Determine force_scan based on scan mode
force_scan = (scan_mode == "full")
shutdown_event = dependencies.get("shutdown_event")
@@ -733,6 +777,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
print(f"✅ MANUAL SCAN COMPLETED: {scan_type} scan (mode: {scan_mode}) finished at {local_end.strftime('%Y-%m-%d %H:%M:%S')}{tz_display}")
print(f"⏱️ MANUAL SCAN DURATION: {duration_str} (total time: {duration.total_seconds():.1f} seconds)")
# Stop scan tracking
stop_scan_tracking()
# Print optimization statistics for TV scans
if scan_type in ["both", "tv"] and tv_series_total > 0:
print(f"📊 TV SCAN OPTIMIZATION: Total: {tv_series_total}, Processed: {tv_series_processed}, Skipped: {tv_series_skipped}")
@@ -1732,6 +1779,113 @@ async def backfill_movie_release_dates(dependencies: dict):
}
# ---------------------------
# Scan Status Functions
# ---------------------------
async def get_scan_status():
"""Get detailed scan status with progress information"""
global scan_status
if not scan_status["scanning"]:
return {"scanning": False, "message": "No active scan"}
# Calculate elapsed time
from datetime import datetime
if scan_status["start_time"]:
elapsed_seconds = int((datetime.now() - scan_status["start_time"]).total_seconds())
if elapsed_seconds >= 60:
minutes = elapsed_seconds // 60
seconds = elapsed_seconds % 60
elapsed_str = f"{minutes}m {seconds}s"
else:
elapsed_str = f"{elapsed_seconds}s"
else:
elapsed_str = "unknown"
# Build detailed status message
if scan_status["current_operation"] == "tv":
if scan_status["tv_series_total"] > 0:
message = f"Processing TV series ({scan_status['tv_series_processed']}/{scan_status['tv_series_total']}) - {elapsed_str} elapsed"
else:
message = f"Processed {scan_status['tv_series_processed']} TV series - {elapsed_str} elapsed"
elif scan_status["current_operation"] == "movies":
if scan_status["movies_total"] > 0:
message = f"Processing movies ({scan_status['movies_processed']}/{scan_status['movies_total']}) - {elapsed_str} elapsed"
else:
message = f"Processed {scan_status['movies_processed']} movies - {elapsed_str} elapsed"
else:
message = f"Scan in progress - {elapsed_str} elapsed"
# Add current item if available
if scan_status["current_item"]:
message += f" | Current: {scan_status['current_item']}"
return {
"scanning": True,
"message": message,
"scan_type": scan_status["scan_type"],
"scan_mode": scan_status["scan_mode"],
"elapsed_seconds": elapsed_seconds if scan_status["start_time"] else 0,
"current_operation": scan_status["current_operation"],
"tv_series_processed": scan_status["tv_series_processed"],
"tv_series_total": scan_status["tv_series_total"],
"tv_series_skipped": scan_status["tv_series_skipped"],
"movies_processed": scan_status["movies_processed"],
"movies_total": scan_status["movies_total"],
"movies_skipped": scan_status["movies_skipped"],
"current_item": scan_status["current_item"]
}
def update_scan_status(operation=None, current_item=None, **kwargs):
"""Update scan status with new progress information"""
global scan_status
if operation:
scan_status["current_operation"] = operation
if current_item:
scan_status["current_item"] = current_item
# Update counters
for key, value in kwargs.items():
if key in scan_status:
scan_status[key] = value
scan_status["last_update"] = datetime.now()
def start_scan_tracking(scan_type, scan_mode):
"""Initialize scan tracking"""
global scan_status
scan_status.update({
"scanning": True,
"scan_type": scan_type,
"scan_mode": scan_mode,
"start_time": datetime.now(),
"current_operation": None,
"tv_series_processed": 0,
"tv_series_total": 0,
"tv_series_skipped": 0,
"movies_processed": 0,
"movies_total": 0,
"movies_skipped": 0,
"current_item": None,
"last_update": datetime.now()
})
def stop_scan_tracking():
"""Stop scan tracking"""
global scan_status
scan_status.update({
"scanning": False,
"scan_type": None,
"scan_mode": None,
"start_time": None,
"current_operation": None,
"current_item": None
})
# ---------------------------
# Route Registration
# ---------------------------
@@ -1822,6 +1976,10 @@ def register_routes(app, dependencies: dict):
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
@app.get("/api/scan/status")
async def _scan_status():
return await get_scan_status()
@app.post("/tv/scan-season")
async def _scan_tv_season(background_tasks: BackgroundTasks, request: TVSeasonRequest):
return await scan_tv_season(background_tasks, request, dependencies)