web: status dash updates
This commit is contained in:
+191
-33
@@ -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)
|
||||
|
||||
+17
-30
@@ -1375,48 +1375,35 @@ def register_web_routes(app, dependencies):
|
||||
|
||||
@app.get("/api/scan/status")
|
||||
async def api_scan_status():
|
||||
"""Check scan status using simple time-based tracking"""
|
||||
"""Proxy scan status requests to core container for detailed progress"""
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Check if we have a recent scan
|
||||
if scan_tracking["last_scan_time"]:
|
||||
time_since_scan = datetime.now() - scan_tracking["last_scan_time"]
|
||||
|
||||
# Assume scan is active for up to 10 minutes after initiation
|
||||
# This is a reasonable estimate for most scan operations
|
||||
if time_since_scan < timedelta(minutes=10) and scan_tracking["scanning"]:
|
||||
minutes_elapsed = int(time_since_scan.total_seconds() / 60)
|
||||
seconds_elapsed = int(time_since_scan.total_seconds() % 60)
|
||||
|
||||
if minutes_elapsed > 0:
|
||||
elapsed_str = f"{minutes_elapsed}m {seconds_elapsed}s"
|
||||
else:
|
||||
elapsed_str = f"{seconds_elapsed}s"
|
||||
|
||||
return {
|
||||
"scanning": True,
|
||||
"message": f"Scan in progress ({elapsed_str} elapsed) - check core logs for details"
|
||||
}
|
||||
|
||||
# Check if core container is responsive
|
||||
# Get core container connection details
|
||||
core_host = os.environ.get("CORE_API_HOST", "nfoguard-core")
|
||||
core_port = os.environ.get("CORE_API_PORT", "8080")
|
||||
|
||||
try:
|
||||
health_url = f"http://{core_host}:{core_port}/health"
|
||||
req = urllib.request.Request(health_url)
|
||||
# Call core container's detailed scan status endpoint
|
||||
status_url = f"http://{core_host}:{core_port}/api/scan/status"
|
||||
req = urllib.request.Request(status_url)
|
||||
|
||||
with urllib.request.urlopen(req, timeout=5) as response:
|
||||
# Mark scanning as false if we reach here and enough time has passed
|
||||
scan_tracking["scanning"] = False
|
||||
return {"scanning": False, "message": "No active scan detected"}
|
||||
status_data = json.loads(response.read().decode())
|
||||
return status_data
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
# Core container doesn't have the endpoint, fallback to simple tracking
|
||||
return {"scanning": False, "message": "Detailed status not available"}
|
||||
else:
|
||||
return {"scanning": False, "message": f"Core container error: {e.code}"}
|
||||
except (urllib.error.URLError, socket.timeout):
|
||||
return {"scanning": False, "message": "Core container unavailable"}
|
||||
except Exception:
|
||||
return {"scanning": False, "message": "Unable to check scan status"}
|
||||
except json.JSONDecodeError:
|
||||
return {"scanning": False, "message": "Invalid response from core container"}
|
||||
except Exception as e:
|
||||
return {"scanning": False, "message": f"Unable to check scan status: {str(e)}"}
|
||||
Reference in New Issue
Block a user