dev #57
+31
-1
@@ -682,7 +682,8 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
||||
try:
|
||||
# Determine force_scan based on scan mode
|
||||
force_scan = (scan_mode == "full")
|
||||
result = movie_processor.process_movie(item, webhook_mode=False, force_scan=force_scan)
|
||||
shutdown_event = dependencies.get("shutdown_event")
|
||||
result = movie_processor.process_movie(item, webhook_mode=False, force_scan=force_scan, shutdown_event=shutdown_event)
|
||||
movie_total += 1
|
||||
if result == "skipped":
|
||||
movie_skipped += 1
|
||||
@@ -691,6 +692,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
||||
elif result == "no_video_files":
|
||||
print(f"INFO: Skipped empty directory: {item.name}")
|
||||
movie_skipped += 1
|
||||
elif result == "shutdown":
|
||||
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping movie scan gracefully")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed processing movie {item}: {e}")
|
||||
movie_total += 1
|
||||
@@ -1207,6 +1211,28 @@ async def cleanup_orphaned_movies(dependencies: dict):
|
||||
}
|
||||
|
||||
|
||||
async def cleanup_orphaned_series(dependencies: dict):
|
||||
"""Find and delete TV series that don't have corresponding directories"""
|
||||
db = dependencies["db"]
|
||||
|
||||
try:
|
||||
deleted_series = db.delete_orphaned_series()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Cleaned up {len(deleted_series)} orphaned TV series",
|
||||
"deleted_count": len(deleted_series),
|
||||
"deleted_series": deleted_series
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": "Failed to cleanup orphaned TV series"
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# Route Registration
|
||||
# ---------------------------
|
||||
@@ -1277,6 +1303,10 @@ def register_routes(app, dependencies: dict):
|
||||
async def _cleanup_orphaned_movies():
|
||||
return await cleanup_orphaned_movies(dependencies)
|
||||
|
||||
@app.post("/database/cleanup/orphaned-series")
|
||||
async def _cleanup_orphaned_series():
|
||||
return await cleanup_orphaned_series(dependencies)
|
||||
|
||||
@app.post("/manual/scan")
|
||||
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)
|
||||
|
||||
@@ -501,6 +501,58 @@ class NFOGuardDatabase:
|
||||
|
||||
return deleted_movies
|
||||
|
||||
def delete_orphaned_series(self) -> List[Dict]:
|
||||
"""
|
||||
Find and delete TV series that don't have corresponding directories on disk
|
||||
This requires checking filesystem for each series, so use carefully
|
||||
|
||||
Returns:
|
||||
List of deleted series with their details
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
deleted_series = []
|
||||
|
||||
with self.get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get all series with their paths
|
||||
cursor.execute("""
|
||||
SELECT imdb_id, path, last_updated, metadata
|
||||
FROM series
|
||||
""")
|
||||
|
||||
series_list = cursor.fetchall()
|
||||
|
||||
for series in series_list:
|
||||
imdb_id = series['imdb_id']
|
||||
series_path = Path(series['path'])
|
||||
|
||||
if not series_path.exists():
|
||||
# Series directory doesn't exist - delete the series and all its episodes
|
||||
cursor.execute("""
|
||||
DELETE FROM episodes
|
||||
WHERE imdb_id = %s
|
||||
""", (imdb_id,))
|
||||
episodes_deleted = cursor.rowcount
|
||||
|
||||
cursor.execute("""
|
||||
DELETE FROM series
|
||||
WHERE imdb_id = %s
|
||||
""", (imdb_id,))
|
||||
|
||||
deleted_series.append({
|
||||
'imdb_id': imdb_id,
|
||||
'reason': 'directory_not_found',
|
||||
'path': str(series_path),
|
||||
'last_updated': series['last_updated'],
|
||||
'episodes_deleted': episodes_deleted
|
||||
})
|
||||
|
||||
conn.commit()
|
||||
|
||||
return deleted_series
|
||||
|
||||
def close(self):
|
||||
"""Close all database connections"""
|
||||
if hasattr(self._local, 'connection'):
|
||||
|
||||
@@ -151,7 +151,7 @@ class MovieProcessor:
|
||||
_log("ERROR", f"Error checking movie completion for {imdb_id}: {e}")
|
||||
return False, f"Error checking completion: {e}"
|
||||
|
||||
def process_movie(self, movie_path: Path, webhook_mode: bool = False, force_scan: bool = False) -> str:
|
||||
def process_movie(self, movie_path: Path, webhook_mode: bool = False, force_scan: bool = False, shutdown_event=None) -> str:
|
||||
"""Process a movie directory"""
|
||||
imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path)
|
||||
if not imdb_id:
|
||||
@@ -180,6 +180,11 @@ class MovieProcessor:
|
||||
else:
|
||||
_log("INFO", f"📥 WEBHOOK PROCESSING MOVIE: {movie_path.name} [{imdb_id}] - Webhook mode")
|
||||
|
||||
# Check for shutdown signal early in processing
|
||||
if shutdown_event and shutdown_event.is_set():
|
||||
_log("INFO", f"⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping movie processing: {movie_path.name}")
|
||||
return "shutdown"
|
||||
|
||||
# Update database
|
||||
self.db.upsert_movie(imdb_id, str(movie_path))
|
||||
|
||||
@@ -309,6 +314,11 @@ class MovieProcessor:
|
||||
|
||||
# TIER 3: No cached data found - proceed with API lookups and verification
|
||||
|
||||
# Check for shutdown signal before expensive API operations
|
||||
if shutdown_event and shutdown_event.is_set():
|
||||
_log("INFO", f"⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping movie processing before API calls: {movie_path.name}")
|
||||
return "shutdown"
|
||||
|
||||
# TIER 3: No cached data found - determine if we should query APIs
|
||||
if webhook_mode:
|
||||
_log("INFO", f"Webhook processing - no cached data found, using full date decision logic")
|
||||
|
||||
Reference in New Issue
Block a user