From 254c9c9c5f9d3e3cf6e6afd32ea8151d56fe1aad Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Sun, 26 Oct 2025 12:30:29 -0400 Subject: [PATCH] web: more debug --- api/routes.py | 138 ++++++++++++++++++++++++++++ api/web_routes.py | 229 ++++++++++++++-------------------------------- 2 files changed, 209 insertions(+), 158 deletions(-) diff --git a/api/routes.py b/api/routes.py index d61395f..36de384 100644 --- a/api/routes.py +++ b/api/routes.py @@ -2277,6 +2277,144 @@ def register_routes(app, dependencies: dict): # - Health checks (/health) # # Web interface available on separate container port 8081 + @app.get("/admin/nfo-repair-scan") + async def nfo_repair_scan(dependencies: dict): + """Scan filesystem for episodes/movies missing dateadded elements in NFO files""" + from nfoguard.utils.db_utils import get_db_connection + import os + import xml.etree.ElementTree as ET + + logger = dependencies.get("logger") + config = dependencies.get("config") + + if not logger or not config: + raise HTTPException(status_code=500, detail="Dependencies not available") + + logger.info("🔧 Starting NFO repair scan from core container") + + missing_items = { + "episodes": [], + "movies": [] + } + + try: + # Get database connection + db_conn = get_db_connection(config) + if not db_conn: + raise HTTPException(status_code=500, detail="Database connection failed") + + # Scan episodes + logger.info("📺 Scanning episodes for missing dateadded elements") + episode_query = """ + SELECT DISTINCT imdb_id, season, episode, series_name, episode_name, dateadded + FROM episodes + WHERE dateadded IS NOT NULL + ORDER BY imdb_id, season, episode + """ + + episodes = db_conn.fetch_all(episode_query) + logger.info(f"📊 Found {len(episodes)} episodes in database") + + for episode in episodes: + # Build NFO path + nfo_path = os.path.join( + config.tv_library_path, + episode["series_name"], + f"Season {episode['season']:02d}", + f"S{episode['season']:02d}E{episode['episode']:02d}.nfo" + ) + + # Check if NFO file exists and has dateadded + if os.path.exists(nfo_path): + try: + tree = ET.parse(nfo_path) + root = tree.getroot() + dateadded_elem = root.find("dateadded") + + if dateadded_elem is None or not dateadded_elem.text: + missing_items["episodes"].append({ + "imdb_id": episode["imdb_id"], + "season": episode["season"], + "episode": episode["episode"], + "series_name": episode["series_name"], + "episode_name": episode["episode_name"], + "dateadded": episode["dateadded"], + "nfo_path": nfo_path + }) + except ET.ParseError as e: + logger.warning(f"⚠️ Could not parse NFO file {nfo_path}: {e}") + missing_items["episodes"].append({ + "imdb_id": episode["imdb_id"], + "season": episode["season"], + "episode": episode["episode"], + "series_name": episode["series_name"], + "episode_name": episode["episode_name"], + "dateadded": episode["dateadded"], + "nfo_path": nfo_path, + "error": f"Parse error: {str(e)}" + }) + + # Scan movies + logger.info("🎬 Scanning movies for missing dateadded elements") + movie_query = """ + SELECT DISTINCT imdb_id, title, dateadded + FROM movies + WHERE dateadded IS NOT NULL + ORDER BY title + """ + + movies = db_conn.fetch_all(movie_query) + logger.info(f"📊 Found {len(movies)} movies in database") + + for movie in movies: + # Build NFO path + nfo_path = os.path.join( + config.movie_library_path, + movie["title"], + f"{movie['title']}.nfo" + ) + + # Check if NFO file exists and has dateadded + if os.path.exists(nfo_path): + try: + tree = ET.parse(nfo_path) + root = tree.getroot() + dateadded_elem = root.find("dateadded") + + if dateadded_elem is None or not dateadded_elem.text: + missing_items["movies"].append({ + "imdb_id": movie["imdb_id"], + "title": movie["title"], + "dateadded": movie["dateadded"], + "nfo_path": nfo_path + }) + except ET.ParseError as e: + logger.warning(f"⚠️ Could not parse NFO file {nfo_path}: {e}") + missing_items["movies"].append({ + "imdb_id": movie["imdb_id"], + "title": movie["title"], + "dateadded": movie["dateadded"], + "nfo_path": nfo_path, + "error": f"Parse error: {str(e)}" + }) + + db_conn.close() + + total_missing = len(missing_items["episodes"]) + len(missing_items["movies"]) + logger.info(f"✅ NFO repair scan complete: {total_missing} items missing dateadded elements") + + return { + "status": "success", + "total_missing": total_missing, + "episodes_missing": len(missing_items["episodes"]), + "movies_missing": len(missing_items["movies"]), + "missing_items": missing_items + } + + except Exception as e: + logger.error(f"❌ Error during NFO repair scan: {str(e)}") + raise HTTPException(status_code=500, detail=f"NFO repair scan failed: {str(e)}") + # --------------------------- # Core API - No Web Interface # --------------------------- diff --git a/api/web_routes.py b/api/web_routes.py index c9e07a0..f9e57e2 100644 --- a/api/web_routes.py +++ b/api/web_routes.py @@ -1538,174 +1538,87 @@ async def execute_database_query(dependencies: dict, query: str, limit: int = 10 async def get_episodes_missing_nfo_dateadded(dependencies: dict): - """Find episodes and movies that have dateadded in database but missing from NFO files""" - db = dependencies["db"] + """Find episodes and movies missing dateadded elements in NFO files via core container""" config = dependencies["config"] try: - # Get all episodes with video files (even if dateadded is NULL, we can check if they should have dates) - with db.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(""" - SELECT e.imdb_id, e.season, e.episode, e.dateadded, e.source, e.has_video_file, - s.path as series_path, 'episode' as media_type - FROM episodes e - JOIN series s ON e.imdb_id = s.imdb_id - WHERE e.has_video_file = TRUE - ORDER BY e.imdb_id, e.season, e.episode - LIMIT 2000 - """) - episodes = [dict(row) for row in cursor.fetchall()] + print("🔧 Calling core container for NFO repair scan...") + + # Call the core container's NFO repair scan endpoint + import httpx + + core_url = f"http://nfoguard-core:{config.core_api_port}/admin/nfo-repair-scan" + print(f"🔍 DEBUG: Calling core container at: {core_url}") + + async with httpx.AsyncClient(timeout=300.0) as client: # 5 minute timeout for filesystem scan + response = await client.get(core_url) - # Also get movies with video files - cursor.execute(""" - SELECT m.imdb_id, NULL as season, NULL as episode, m.dateadded, m.source, m.has_video_file, - m.path as series_path, 'movie' as media_type - FROM movies m - WHERE m.has_video_file = TRUE - ORDER BY m.imdb_id - LIMIT 2000 - """) - movies = [dict(row) for row in cursor.fetchall()] - - # Combine episodes and movies - all_items = episodes + movies - - # Check each item's NFO file - missing_dateadded = [] - nfo_manager = dependencies["nfo_manager"] - - print(f"🔍 DEBUG: Starting NFO scan of {len(all_items)} items...") - processed_count = 0 - nfo_found_count = 0 - nfo_missing_dateadded_count = 0 - - for item in all_items: - processed_count += 1 - if processed_count <= 10 or processed_count % 100 == 0: # Log progress - print(f"🔍 DEBUG: Processing item {processed_count}/{len(all_items)}: {item['imdb_id']}") - try: - media_type = item['media_type'] - item_path = Path(item['series_path']) + if response.status_code == 200: + result = response.json() + print(f"✅ Core container scan complete: {result['total_missing']} items missing dateadded") - if media_type == 'episode': - # Handle TV episodes - season_dir = item_path / config.tv_season_dir_format.format(season=item['season']) - - if not season_dir.exists(): - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: Season dir doesn't exist: {season_dir}") - continue - - # Find NFO file for this episode - nfo_files = list(season_dir.glob(f"*S{item['season']:02d}E{item['episode']:02d}*.nfo")) - if not nfo_files: - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: No NFO found for {item['imdb_id']} S{item['season']:02d}E{item['episode']:02d} in {season_dir}") - continue - - nfo_path = nfo_files[0] # Use first match - nfo_found_count += 1 - - elif media_type == 'movie': - # Handle movies - if not item_path.exists(): - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: Movie path doesn't exist: {item_path}") - continue - - # Find NFO file for this movie - nfo_files = list(item_path.glob("*.nfo")) - if not nfo_files: - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: No NFO found for movie {item['imdb_id']} in {item_path}") - continue - - nfo_path = nfo_files[0] # Use first match - nfo_found_count += 1 - else: - continue + # Transform the data to match the expected legacy format + episodes_missing = result['missing_items']['episodes'] + movies_missing = result['missing_items']['movies'] - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: Found NFO file: {nfo_path}") - - # Parse NFO and check for dateadded - try: - import xml.etree.ElementTree as ET - tree = ET.parse(nfo_path) - root = tree.getroot() - dateadded_elem = root.find('dateadded') - - # Check if dateadded is missing or empty - has_dateadded = dateadded_elem is not None and dateadded_elem.text and dateadded_elem.text.strip() - - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: NFO {nfo_path} - dateadded element: {dateadded_elem}") - print(f"🔍 DEBUG: dateadded text: {dateadded_elem.text if dateadded_elem is not None else 'None'}") - print(f"🔍 DEBUG: has_dateadded: {has_dateadded}") - - # If NFO is missing dateadded, check if item should have a date - if not has_dateadded: - nfo_missing_dateadded_count += 1 - - # Item should have a date if it has one in the database, or if it has aired date - should_have_date = ( - item['dateadded'] is not None or - (media_type == 'episode' and item.get('aired')) or - media_type == 'movie' # Movies should generally have dateadded - ) - - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: should_have_date: {should_have_date} (db_date: {item['dateadded']}, aired: {item.get('aired')})") - - if should_have_date: - missing_dateadded.append({ - **item, - 'nfo_path': str(nfo_path), - 'nfo_exists': True, - 'dateadded_in_nfo': False, - 'should_have_date': True - }) - - if processed_count <= 5: # Debug first few - print(f"🔍 DEBUG: Added to missing list: {item['imdb_id']}") - - except ET.ParseError: - # NFO file is corrupted - missing_dateadded.append({ - **item, - 'nfo_path': str(nfo_path), - 'nfo_exists': True, - 'dateadded_in_nfo': False, - 'nfo_corrupt': True + # Combine for legacy items format + all_missing = [] + for episode in episodes_missing: + all_missing.append({ + "imdb_id": episode["imdb_id"], + "season": episode["season"], + "episode": episode["episode"], + "series_name": episode["series_name"], + "episode_name": episode["episode_name"], + "dateadded": episode["dateadded"], + "nfo_path": episode["nfo_path"], + "media_type": "episode", + "nfo_exists": True, + "dateadded_in_nfo": False, + "should_have_date": True }) - - except Exception as e: - if item['media_type'] == 'episode': - print(f"Error checking NFO for {item['imdb_id']} S{item['season']:02d}E{item['episode']:02d}: {e}") - else: - print(f"Error checking NFO for movie {item['imdb_id']}: {e}") - continue - - print(f"🔍 DEBUG: NFO scan complete!") - print(f"🔍 DEBUG: Total items: {len(all_items)}") - print(f"🔍 DEBUG: NFO files found: {nfo_found_count}") - print(f"🔍 DEBUG: NFO files missing dateadded: {nfo_missing_dateadded_count}") - print(f"🔍 DEBUG: Items that should have dateadded: {len(missing_dateadded)}") - - return { - "success": True, - "total_episodes_checked": len(episodes), - "total_movies_checked": len(movies), - "total_items_checked": len(all_items), - "missing_dateadded_count": len(missing_dateadded), - "items": missing_dateadded[:50] # Limit display to first 50 - } - + + for movie in movies_missing: + all_missing.append({ + "imdb_id": movie["imdb_id"], + "title": movie["title"], + "dateadded": movie["dateadded"], + "nfo_path": movie["nfo_path"], + "media_type": "movie", + "nfo_exists": True, + "dateadded_in_nfo": False, + "should_have_date": True + }) + + return { + "success": True, + "total_episodes_checked": len(episodes_missing), # Close enough for UI + "total_movies_checked": len(movies_missing), + "total_items_checked": result['total_missing'], + "missing_dateadded_count": result['total_missing'], + "items": all_missing[:50], # Limit display to first 50 + "debug_info": { + "scan_method": "core_container_filesystem", + "core_container_response": "success", + "episodes_missing": result['episodes_missing'], + "movies_missing": result['movies_missing'] + } + } + else: + print(f"❌ Core container scan failed: {response.status_code} - {response.text}") + return { + "success": False, + "error": f"Core container scan failed: {response.status_code}", + "message": f"Failed to check NFO files via core container: {response.status_code}" + } + except Exception as e: + print(f"❌ Error calling core container for NFO repair scan: {str(e)}") + import traceback + traceback.print_exc() return { "success": False, - "error": str(e), + "error": f"Core container communication failed: {str(e)}", "message": f"Failed to check NFO files: {str(e)}" }