This commit is contained in:
+138
@@ -2277,6 +2277,144 @@ def register_routes(app, dependencies: dict):
|
|||||||
# - Health checks (/health)
|
# - Health checks (/health)
|
||||||
#
|
#
|
||||||
# Web interface available on separate container port 8081
|
# 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
|
# Core API - No Web Interface
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|||||||
+71
-158
@@ -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):
|
async def get_episodes_missing_nfo_dateadded(dependencies: dict):
|
||||||
"""Find episodes and movies that have dateadded in database but missing from NFO files"""
|
"""Find episodes and movies missing dateadded elements in NFO files via core container"""
|
||||||
db = dependencies["db"]
|
|
||||||
config = dependencies["config"]
|
config = dependencies["config"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get all episodes with video files (even if dateadded is NULL, we can check if they should have dates)
|
print("🔧 Calling core container for NFO repair scan...")
|
||||||
with db.get_connection() as conn:
|
|
||||||
cursor = conn.cursor()
|
# Call the core container's NFO repair scan endpoint
|
||||||
cursor.execute("""
|
import httpx
|
||||||
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
|
core_url = f"http://nfoguard-core:{config.core_api_port}/admin/nfo-repair-scan"
|
||||||
FROM episodes e
|
print(f"🔍 DEBUG: Calling core container at: {core_url}")
|
||||||
JOIN series s ON e.imdb_id = s.imdb_id
|
|
||||||
WHERE e.has_video_file = TRUE
|
async with httpx.AsyncClient(timeout=300.0) as client: # 5 minute timeout for filesystem scan
|
||||||
ORDER BY e.imdb_id, e.season, e.episode
|
response = await client.get(core_url)
|
||||||
LIMIT 2000
|
|
||||||
""")
|
|
||||||
episodes = [dict(row) for row in cursor.fetchall()]
|
|
||||||
|
|
||||||
# Also get movies with video files
|
if response.status_code == 200:
|
||||||
cursor.execute("""
|
result = response.json()
|
||||||
SELECT m.imdb_id, NULL as season, NULL as episode, m.dateadded, m.source, m.has_video_file,
|
print(f"✅ Core container scan complete: {result['total_missing']} items missing dateadded")
|
||||||
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 media_type == 'episode':
|
# Transform the data to match the expected legacy format
|
||||||
# Handle TV episodes
|
episodes_missing = result['missing_items']['episodes']
|
||||||
season_dir = item_path / config.tv_season_dir_format.format(season=item['season'])
|
movies_missing = result['missing_items']['movies']
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if processed_count <= 5: # Debug first few
|
# Combine for legacy items format
|
||||||
print(f"🔍 DEBUG: Found NFO file: {nfo_path}")
|
all_missing = []
|
||||||
|
for episode in episodes_missing:
|
||||||
# Parse NFO and check for dateadded
|
all_missing.append({
|
||||||
try:
|
"imdb_id": episode["imdb_id"],
|
||||||
import xml.etree.ElementTree as ET
|
"season": episode["season"],
|
||||||
tree = ET.parse(nfo_path)
|
"episode": episode["episode"],
|
||||||
root = tree.getroot()
|
"series_name": episode["series_name"],
|
||||||
dateadded_elem = root.find('dateadded')
|
"episode_name": episode["episode_name"],
|
||||||
|
"dateadded": episode["dateadded"],
|
||||||
# Check if dateadded is missing or empty
|
"nfo_path": episode["nfo_path"],
|
||||||
has_dateadded = dateadded_elem is not None and dateadded_elem.text and dateadded_elem.text.strip()
|
"media_type": "episode",
|
||||||
|
"nfo_exists": True,
|
||||||
if processed_count <= 5: # Debug first few
|
"dateadded_in_nfo": False,
|
||||||
print(f"🔍 DEBUG: NFO {nfo_path} - dateadded element: {dateadded_elem}")
|
"should_have_date": True
|
||||||
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
|
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
for movie in movies_missing:
|
||||||
if item['media_type'] == 'episode':
|
all_missing.append({
|
||||||
print(f"Error checking NFO for {item['imdb_id']} S{item['season']:02d}E{item['episode']:02d}: {e}")
|
"imdb_id": movie["imdb_id"],
|
||||||
else:
|
"title": movie["title"],
|
||||||
print(f"Error checking NFO for movie {item['imdb_id']}: {e}")
|
"dateadded": movie["dateadded"],
|
||||||
continue
|
"nfo_path": movie["nfo_path"],
|
||||||
|
"media_type": "movie",
|
||||||
print(f"🔍 DEBUG: NFO scan complete!")
|
"nfo_exists": True,
|
||||||
print(f"🔍 DEBUG: Total items: {len(all_items)}")
|
"dateadded_in_nfo": False,
|
||||||
print(f"🔍 DEBUG: NFO files found: {nfo_found_count}")
|
"should_have_date": True
|
||||||
print(f"🔍 DEBUG: NFO files missing dateadded: {nfo_missing_dateadded_count}")
|
})
|
||||||
print(f"🔍 DEBUG: Items that should have dateadded: {len(missing_dateadded)}")
|
|
||||||
|
return {
|
||||||
return {
|
"success": True,
|
||||||
"success": True,
|
"total_episodes_checked": len(episodes_missing), # Close enough for UI
|
||||||
"total_episodes_checked": len(episodes),
|
"total_movies_checked": len(movies_missing),
|
||||||
"total_movies_checked": len(movies),
|
"total_items_checked": result['total_missing'],
|
||||||
"total_items_checked": len(all_items),
|
"missing_dateadded_count": result['total_missing'],
|
||||||
"missing_dateadded_count": len(missing_dateadded),
|
"items": all_missing[:50], # Limit display to first 50
|
||||||
"items": missing_dateadded[: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:
|
except Exception as e:
|
||||||
|
print(f"❌ Error calling core container for NFO repair scan: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": str(e),
|
"error": f"Core container communication failed: {str(e)}",
|
||||||
"message": f"Failed to check NFO files: {str(e)}"
|
"message": f"Failed to check NFO files: {str(e)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user