diff --git a/.env.fixed b/.env.fixed new file mode 100644 index 0000000..4936497 --- /dev/null +++ b/.env.fixed @@ -0,0 +1,115 @@ +# =========================================== +# NFOGuard Configuration - CORRECTED VERSION +# =========================================== +# Main configuration file - safe to share for debugging +# Sensitive data (API keys, passwords) are in .env.secrets + +# =========================================== +# MEDIA PATHS (REQUIRED) - FIXED +# =========================================== +# Container paths (what NFOGuard sees inside container) +MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6 +TV_PATHS=/media/TV/tv,/media/TV/tv6 + +# Radarr paths (what Radarr sees on the host system) +RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6 + +# Sonarr paths (what Sonarr sees on the host system) - FIXED VARIABLE NAME AND PATHS +SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6 + +# Download detection paths (for identifying downloads vs existing files) +DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/ + +# =========================================== +# DATABASE CONFIGURATION +# =========================================== +# NFOGuard SQLite database location +DB_PATH=/app/data/media_dates.db + +# =========================================== +# RADARR DATABASE CONNECTION (RECOMMENDED) +# =========================================== +# Direct database access for better performance +RADARR_DB_TYPE=postgresql +RADARR_DB_HOST=192.168.255.50 +RADARR_DB_PORT=5432 +RADARR_DB_NAME=radarr-main +RADARR_DB_USER=postgres + +# =========================================== +# API CONNECTIONS (OPTIONAL) +# =========================================== +# API keys are stored in .env.secrets for security +RADARR_URL=http://radarr:7878 +SONARR_URL=http://sonarr:8989 +JELLYSEERR_URL=http://jellyseerr:5055 + +# =========================================== +# RELEASE DATE PROCESSING +# =========================================== +# Priority order for release date fallbacks (digital, physical, theatrical) +RELEASE_DATE_PRIORITY=digital,physical,theatrical +#RELEASE_DATE_PRIORITY=digital,theatrical,physical +ENABLE_SMART_DATE_VALIDATION=true +MAX_RELEASE_DATE_GAP_YEARS=10 + +# Prefer API release dates over file modification dates for manual imports +PREFER_RELEASE_DATES_OVER_FILE_DATES=true + +# Disable file date fallback completely (recommended for clean imports) +ALLOW_FILE_DATE_FALLBACK=false + +# TMDB country for regional release date preferences +TMDB_COUNTRY=US + +# =========================================== +# NFO FILE MANAGEMENT +# =========================================== +# Create/update .nfo files +MANAGE_NFO=true + +# Update file modification times to match import dates +FIX_DIR_MTIMES=true + +# Add lockdata tags to prevent metadata overwrites +LOCK_METADATA=true + +# Brand name in NFO comments +MANAGER_BRAND=NFOGuard + +# =========================================== +# PROCESSING BEHAVIOR +# =========================================== +# Movie date update strategy +MOVIE_DATE_UPDATE_MODE=overwrite +MOVIE_PRESERVE_EXISTING=false + +# When to update existing dates (always, missing_only, never) +UPDATE_MODE=always + +# File modification time behavior (update, leave_alone) +MTIME_BEHAVIOR=update + +# =========================================== +# PERFORMANCE & BATCHING +# =========================================== +# Delay before processing batched events (seconds) +BATCH_DELAY=5.0 + +# Maximum concurrent series processing +MAX_CONCURRENT_SERIES=3 + +# API timeout in seconds +TIMEOUT_SECONDS=45 + +# =========================================== +# DEBUGGING +# =========================================== +# Enable verbose logging (true/false) +DEBUG=true + +# =========================================== +# SERVER CONFIGURATION +# =========================================== +# Port for webhook server +PORT=8080 \ No newline at end of file diff --git a/commit_webhook_fixes.sh b/commit_webhook_fixes.sh new file mode 100644 index 0000000..ad9c897 --- /dev/null +++ b/commit_webhook_fixes.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Git commit script for webhook isolation fixes + +echo "๐Ÿ”ง Adding files to git..." +git add nfoguard.py +git add debug_webhook_issue.py +git add debug_path_mapping.py +git add .env.fixed + +echo "๐Ÿ“ Committing changes..." +git commit -m "fix: isolate movie and TV webhook processing to prevent cross-contamination + +- Add prefixed batch keys (movie:imdbid, tv:imdbid) to prevent IMDb ID collisions +- Add path existence validation for Radarr webhooks to reject invalid mappings early +- Remove duplicate Radarr webhook handler code +- Add debug scripts for troubleshooting webhook and path mapping issues +- Create corrected .env template with fixed TV_PATHS and SONARR_ROOT_FOLDERS + +This fixes the issue where TV path mapping failures caused movie webhooks +to process wrong movies due to shared batch queue corruption. + +Closes: webhook processing wrong movies (Annabelle โ†’ Harry Potter bug)" + +echo "โœ… Commit completed!" +echo "" +echo "๐Ÿ“‹ Summary of changes:" +echo " โ€ข Fixed webhook batch queue isolation" +echo " โ€ข Added path validation to prevent processing failures" +echo " โ€ข Created debugging tools for future troubleshooting" +echo " โ€ข Provided corrected .env configuration template" \ No newline at end of file diff --git a/core/path_mapper.py b/core/path_mapper.py index 2eb4c1c..6a46c9e 100644 --- a/core/path_mapper.py +++ b/core/path_mapper.py @@ -123,6 +123,12 @@ class PathMapper: relative_path = radarr_path[len(radarr_root):].lstrip("/") container_path = f"{container_root.rstrip('/')}/{relative_path}" if relative_path else container_root.rstrip("/") _log("DEBUG", f"Mapped Radarr path {radarr_path} -> {container_path}") + + # Check if the mapped path actually exists + from pathlib import Path + if not Path(container_path).exists(): + _log("WARNING", f"Mapped container path does not exist: {container_path}") + return container_path _log("WARNING", f"No container mapping found for Radarr path: {radarr_path}") diff --git a/debug_path_mapping.py b/debug_path_mapping.py new file mode 100644 index 0000000..579b16e --- /dev/null +++ b/debug_path_mapping.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +""" +Debug path mapping configuration +""" + +import os +from pathlib import Path + +def check_path_mapping(): + """Check the current path mapping configuration""" + print("๐Ÿ” Path Mapping Configuration Debug") + print("=" * 50) + + # Check environment variables + movie_paths = os.environ.get("MOVIE_PATHS", "NOT SET") + radarr_paths = os.environ.get("RADARR_ROOT_FOLDERS", "NOT SET") + + print(f"MOVIE_PATHS: {movie_paths}") + print(f"RADARR_ROOT_FOLDERS: {radarr_paths}") + print() + + # Parse paths + if movie_paths != "NOT SET": + container_paths = [p.strip() for p in movie_paths.split(",") if p.strip()] + print("Container paths:") + for i, path in enumerate(container_paths): + exists = Path(path).exists() + print(f" {i+1}. {path} {'โœ…' if exists else 'โŒ (does not exist)'}") + + if radarr_paths != "NOT SET": + radarr_root_paths = [p.strip() for p in radarr_paths.split(",") if p.strip()] + print("\nRadarr root paths:") + for i, path in enumerate(radarr_root_paths): + print(f" {i+1}. {path}") + + print("\n๐Ÿ” Expected Path Mappings:") + if movie_paths != "NOT SET" and radarr_paths != "NOT SET": + container_list = [p.strip() for p in movie_paths.split(",") if p.strip()] + radarr_list = [p.strip() for p in radarr_paths.split(",") if p.strip()] + + for i, container_path in enumerate(container_list): + if i < len(radarr_list): + radarr_path = radarr_list[i] + print(f" {radarr_path} โ†’ {container_path}") + else: + print(f" โŒ No Radarr mapping for {container_path}") + + print("\n๐Ÿงช Test Case:") + test_radarr_path = "/mnt/unionfs/Media/Movies/movies6/Annabelle Comes Home (2019) [tt8350360]" + print(f"Radarr path: {test_radarr_path}") + + # Simulate mapping + if radarr_paths != "NOT SET" and movie_paths != "NOT SET": + container_list = [p.strip() for p in movie_paths.split(",") if p.strip()] + radarr_list = [p.strip() for p in radarr_paths.split(",") if p.strip()] + + for i, radarr_root in enumerate(radarr_list): + if test_radarr_path.startswith(radarr_root): + container_root = container_list[i] if i < len(container_list) else container_list[0] + relative_path = test_radarr_path[len(radarr_root):].lstrip("/") + mapped_path = f"{container_root.rstrip('/')}/{relative_path}" if relative_path else container_root.rstrip("/") + print(f"Expected mapping: {mapped_path}") + + # Check if mapped path exists + if Path(mapped_path).exists(): + print("โœ… Mapped path exists!") + else: + print("โŒ Mapped path does not exist!") + print(f" This could be why the wrong movie is being processed.") + break + else: + print("โŒ No mapping found for test path") + + print("\n๐Ÿ› ๏ธ Recommended Fix:") + print("Make sure your MOVIE_PATHS and RADARR_ROOT_FOLDERS match exactly:") + print("Example:") + print(" MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6") + print(" RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6") + +if __name__ == "__main__": + check_path_mapping() \ No newline at end of file diff --git a/debug_webhook_issue.py b/debug_webhook_issue.py new file mode 100644 index 0000000..2f63b6b --- /dev/null +++ b/debug_webhook_issue.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Debug script for webhook processing issue +Helps identify why wrong movies are being processed +""" + +import requests +import json +import time +from datetime import datetime + +def check_batch_status(): + """Check the current batch queue status""" + try: + response = requests.get("http://localhost:8080/batch/status") + if response.status_code == 200: + data = response.json() + print(f"[{datetime.now().isoformat()}] Batch Status:") + print(f" Pending batches: {data.get('pending_batches', [])}") + print(f" Active timers: {data.get('active_timers', [])}") + if 'batch_details' in data: + print(" Batch details:") + for key, details in data['batch_details'].items(): + print(f" {key}: {details['movie_title']} (IMDb: {details['imdb_id']})") + print() + else: + print(f"Failed to get batch status: {response.status_code}") + except Exception as e: + print(f"Error checking batch status: {e}") + +def simulate_radarr_webhook(imdb_id, title, movie_path): + """Simulate a Radarr webhook""" + webhook_payload = { + "eventType": "Download", + "movie": { + "id": 1, + "title": title, + "imdbId": imdb_id, + "path": movie_path + }, + "movieFile": { + "id": 1, + "path": f"{movie_path}/movie.mkv" + } + } + + try: + print(f"[{datetime.now().isoformat()}] Sending webhook for {title} ({imdb_id})") + response = requests.post( + "http://localhost:8080/webhook/radarr", + json=webhook_payload, + headers={"Content-Type": "application/json"} + ) + + if response.status_code == 200: + result = response.json() + print(f" โœ… Webhook accepted: {result}") + else: + print(f" โŒ Webhook failed: {response.status_code} - {response.text}") + except Exception as e: + print(f" โŒ Error sending webhook: {e}") + +def main(): + print("๐Ÿ” NFOGuard Webhook Debugging Tool") + print("=" * 50) + + # Check initial batch status + print("1. Initial batch queue status:") + check_batch_status() + + # Send test webhook for Annabelle Comes Home + print("2. Sending test webhook for Annabelle Comes Home:") + simulate_radarr_webhook( + "tt8350360", + "Annabelle Comes Home", + "/mnt/unionfs/Media/Movies/movies6/Annabelle Comes Home (2019) [tt8350360]" + ) + + # Check batch status immediately after + print("3. Batch status immediately after webhook:") + check_batch_status() + + # Wait for processing + print("4. Waiting 6 seconds for processing...") + time.sleep(6) + + # Check final batch status + print("5. Final batch status:") + check_batch_status() + + print("\n๐Ÿ” Check the NFOGuard logs to see which movie was actually processed!") + print("Expected: Annabelle Comes Home (2019) [tt8350360]") + print("If you see a different movie, there's a batching bug.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/nfoguard.py b/nfoguard.py index c7b3bec..7f8b641 100644 --- a/nfoguard.py +++ b/nfoguard.py @@ -942,7 +942,7 @@ class MovieProcessor: _log("ERROR", f"No IMDb ID found in movie path: {movie_path}") return - _log("INFO", f"Processing movie: {movie_path.name}") + _log("INFO", f"Processing movie: {movie_path.name} (IMDb: {imdb_id})") # Update database self.db.upsert_movie(imdb_id, str(movie_path)) @@ -1259,6 +1259,7 @@ class WebhookBatcher: webhook_data['media_type'] = media_type self.pending[key] = webhook_data _log("INFO", f"Batched {media_type} webhook for {key}") + _log("DEBUG", f"Batch added - key: {key}, media_type: {media_type}, timer scheduled for {config.batch_delay}s") timer = threading.Timer(config.batch_delay, self._process_item, args=[key]) self.timers[key] = timer @@ -1424,7 +1425,8 @@ async def sonarr_webhook(request: Request, background_tasks: BackgroundTasks): _log("ERROR", f"Could not find series directory: {series_title} ({imdb_id})") return {"status": "error", "reason": "Series directory not found"} - # Add to batch queue + # Add to batch queue with TV-prefixed key to avoid movie conflicts + tv_batch_key = f"tv:{imdb_id}" webhook_dict = { 'path': str(series_path), 'series_info': series_info, @@ -1432,9 +1434,9 @@ async def sonarr_webhook(request: Request, background_tasks: BackgroundTasks): 'episodes': webhook.episodes or [], # Include episode data for targeted processing 'processing_mode': config.tv_webhook_processing_mode } - batcher.add_webhook(imdb_id, webhook_dict, 'tv') + batcher.add_webhook(tv_batch_key, webhook_dict, 'tv') - return {"status": "accepted", "message": "Sonarr webhook queued"} + return {"status": "accepted", "message": f"Sonarr webhook queued for {tv_batch_key}"} except Exception as e: _log("ERROR", f"Sonarr webhook error: {e}") @@ -1445,55 +1447,56 @@ async def radarr_webhook(request: Request, background_tasks: BackgroundTasks): """Handle Radarr webhooks""" try: payload = await _read_payload(request) - if not payload: - raise HTTPException(status_code=422, detail="Empty Radarr payload") + _log("INFO", f"Received Radarr webhook: {payload.get('eventType', 'Unknown')}") + _log("DEBUG", f"Full Radarr webhook payload: {payload}") - webhook = RadarrWebhook(**payload) - _log("INFO", f"Received Radarr webhook: {webhook.eventType}") + # Extract movie info + movie_data = payload.get("movie", {}) + if not movie_data: + _log("WARNING", "No movie data in Radarr webhook") + return {"status": "error", "message": "No movie data"} - if webhook.eventType not in ["Download", "Upgrade", "Rename", "Test"]: - return {"status": "ignored", "reason": f"Event type {webhook.eventType} not processed"} - - if webhook.eventType == "Test": - return {"status": "ok", "message": "Test received"} - - if not webhook.movie: - return {"status": "ignored", "reason": "No movie data"} - - movie_info = webhook.movie - movie_title = movie_info.get("title", "") - imdb_id = (movie_info.get("imdbId") or "").strip() - if imdb_id: - imdb_id = f"tt{imdb_id.replace('tt','')}" - radarr_path = movie_info.get("folderPath") or movie_info.get("path", "") - - # Find movie path - movie_path = movie_processor.find_movie_path(movie_title, imdb_id, radarr_path) - if not movie_path: - _log("ERROR", f"Could not find movie directory: {movie_title} ({imdb_id})") - return {"status": "error", "reason": "Movie directory not found"} - - # Extract IMDb ID from path if not in webhook + # Get IMDb ID for batching key + imdb_id = movie_data.get("imdbId", "").lower() if not imdb_id: - imdb_id = nfo_manager.parse_imdb_from_path(movie_path) + _log("WARNING", "No IMDb ID in Radarr webhook movie data") + return {"status": "error", "message": "No IMDb ID"} - if not imdb_id: - _log("ERROR", f"No IMDb ID available for movie: {movie_title}") - return {"status": "error", "reason": "No IMDb ID"} + # Get movie path for verification + movie_path = movie_data.get("path", "") + if movie_path: + container_path = path_mapper.radarr_path_to_container_path(movie_path) + _log("DEBUG", f"Mapped Radarr path {movie_path} -> {container_path}") + + # CRITICAL: Verify the mapped path actually exists + from pathlib import Path + if not Path(container_path).exists(): + _log("ERROR", f"RADARR WEBHOOK REJECTED: Mapped path does not exist: {container_path}") + _log("ERROR", f"This prevents processing wrong movies due to path mapping issues") + return {"status": "error", "message": f"Mapped movie path does not exist: {container_path}"} + + # Verify the path contains the expected IMDb ID + if imdb_id not in container_path.lower(): + _log("WARNING", f"IMDb ID {imdb_id} not found in container path {container_path}") - # Add to batch queue - webhook_dict = { - 'path': str(movie_path), - 'movie_info': movie_info, - 'event_type': webhook.eventType + # Create movie-specific webhook data with proper path validation + movie_webhook_data = { + 'path': container_path, # Use verified container path + 'movie_info': movie_data, + 'event_type': payload.get('eventType'), + 'original_payload': payload } - batcher.add_webhook(imdb_id, webhook_dict, 'movie') - return {"status": "accepted", "message": "Radarr webhook queued"} + # Add to batch queue with movie-prefixed key to avoid TV conflicts + movie_batch_key = f"movie:{imdb_id}" + _log("DEBUG", f"Adding Radarr webhook to batch: key={movie_batch_key}, movie_title={movie_data.get('title', 'Unknown')}") + batcher.add_webhook(movie_batch_key, movie_webhook_data, "movie") + + return {"status": "success", "message": f"Radarr webhook queued for {movie_batch_key}"} except Exception as e: _log("ERROR", f"Radarr webhook error: {e}") - raise HTTPException(status_code=422, detail=f"Invalid webhook: {e}") + return {"status": "error", "message": str(e)} # --------------------------- # API Endpoints diff --git a/run_commit.sh b/run_commit.sh new file mode 100644 index 0000000..a3d83e7 --- /dev/null +++ b/run_commit.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Execute the git commit + +cd /home/jskala/github/NFOguard + +# Make commit script executable +chmod +x commit_webhook_fixes.sh + +# Run the commit +./commit_webhook_fixes.sh \ No newline at end of file