Compare commits
2 Commits
55de6284c8
...
445206de4b
| Author | SHA1 | Date | |
|---|---|---|---|
| 445206de4b | |||
| 1e2cf49c10 |
+31
-1
@@ -682,7 +682,8 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
try:
|
try:
|
||||||
# Determine force_scan based on scan mode
|
# Determine force_scan based on scan mode
|
||||||
force_scan = (scan_mode == "full")
|
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
|
movie_total += 1
|
||||||
if result == "skipped":
|
if result == "skipped":
|
||||||
movie_skipped += 1
|
movie_skipped += 1
|
||||||
@@ -691,6 +692,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
elif result == "no_video_files":
|
elif result == "no_video_files":
|
||||||
print(f"INFO: Skipped empty directory: {item.name}")
|
print(f"INFO: Skipped empty directory: {item.name}")
|
||||||
movie_skipped += 1
|
movie_skipped += 1
|
||||||
|
elif result == "shutdown":
|
||||||
|
print("INFO: ⚠️ SHUTDOWN SIGNAL RECEIVED - Stopping movie scan gracefully")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed processing movie {item}: {e}")
|
print(f"ERROR: Failed processing movie {item}: {e}")
|
||||||
movie_total += 1
|
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
|
# Route Registration
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
@@ -1277,6 +1303,10 @@ def register_routes(app, dependencies: dict):
|
|||||||
async def _cleanup_orphaned_movies():
|
async def _cleanup_orphaned_movies():
|
||||||
return await cleanup_orphaned_movies(dependencies)
|
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")
|
@app.post("/manual/scan")
|
||||||
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
|
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)
|
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
|
||||||
|
|||||||
@@ -501,6 +501,58 @@ class NFOGuardDatabase:
|
|||||||
|
|
||||||
return deleted_movies
|
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):
|
def close(self):
|
||||||
"""Close all database connections"""
|
"""Close all database connections"""
|
||||||
if hasattr(self._local, 'connection'):
|
if hasattr(self._local, 'connection'):
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class MovieProcessor:
|
|||||||
_log("ERROR", f"Error checking movie completion for {imdb_id}: {e}")
|
_log("ERROR", f"Error checking movie completion for {imdb_id}: {e}")
|
||||||
return False, f"Error checking completion: {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"""
|
"""Process a movie directory"""
|
||||||
imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path)
|
imdb_id = self.nfo_manager.find_movie_imdb_id(movie_path)
|
||||||
if not imdb_id:
|
if not imdb_id:
|
||||||
@@ -180,6 +180,11 @@ class MovieProcessor:
|
|||||||
else:
|
else:
|
||||||
_log("INFO", f"📥 WEBHOOK PROCESSING MOVIE: {movie_path.name} [{imdb_id}] - Webhook mode")
|
_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
|
# Update database
|
||||||
self.db.upsert_movie(imdb_id, str(movie_path))
|
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
|
# 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
|
# TIER 3: No cached data found - determine if we should query APIs
|
||||||
if webhook_mode:
|
if webhook_mode:
|
||||||
_log("INFO", f"Webhook processing - no cached data found, using full date decision logic")
|
_log("INFO", f"Webhook processing - no cached data found, using full date decision logic")
|
||||||
|
|||||||
Reference in New Issue
Block a user