diff --git a/.env.fixed b/.env.fixed index 4936497..3ff31f4 100644 --- a/.env.fixed +++ b/.env.fixed @@ -8,7 +8,7 @@ # MEDIA PATHS (REQUIRED) - FIXED # =========================================== # Container paths (what NFOGuard sees inside container) -MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6 +MOVIE_PATHS=/media/movies,/media/movies6 TV_PATHS=/media/TV/tv,/media/TV/tv6 # Radarr paths (what Radarr sees on the host system) diff --git a/commit_webhook_fixes.sh b/commit_webhook_fixes.sh deleted file mode 100644 index ad9c897..0000000 --- a/commit_webhook_fixes.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/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/commit_webhook_path_fix.sh b/commit_webhook_path_fix.sh deleted file mode 100644 index b71bce7..0000000 --- a/commit_webhook_path_fix.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -# Git commit script for webhook path resolution fixes - -echo "🔧 Adding files to git..." -git add nfoguard.py -git add SUMMARY.md -git add VERSION -git add debug_webhook_isolation.py -git add .env.fixed - -echo "📝 Committing changes..." -git commit -m "fix: resolve Radarr webhook path variable bug and enhance validation - -## Critical Bug Fix -- Fixed 'container_path' undefined variable error in Radarr webhook handler -- Webhook now properly handles both 'folderPath' and 'path' fields -- Added mandatory path validation before processing - -## Enhanced Webhook Validation -- Verify mapped paths exist before batch processing -- Add IMDb ID validation in batch processor to prevent wrong movie processing -- Improved error logging with specific failure reasons - -## Path Mapping Improvements -- Support both folderPath (new Radarr format) and path (legacy) fields -- Early rejection of webhooks with invalid path mappings -- Better error messages for debugging path issues - -## Version & Documentation Updates -- Bump version to v1.3.1 with comprehensive changelog -- Updated SUMMARY.md with detailed webhook isolation analysis -- Added debug script for testing webhook processing - -## Fixes Issue -The webhook was failing with 'cannot access local variable container_path' -because folderPath wasn't being handled, causing the validation block to -reference an undefined variable. - -## Test Results -✅ Webhook accepts valid paths and processes correct movies -✅ Webhook rejects invalid paths early to prevent wrong processing -✅ Batch validation ensures IMDb ID matches path before processing -✅ No more Annabelle → Harry Potter cross-processing bugs" - -echo "✅ Commit completed!" -echo "" -echo "📋 Files committed:" -echo " • nfoguard.py (webhook path bug fix + validation)" -echo " • SUMMARY.md (comprehensive analysis + v1.3.1 details)" -echo " • VERSION (bumped to 1.3.1)" -echo " • debug_webhook_isolation.py (testing tool)" -echo " • .env.fixed (corrected configuration template)" -echo "" -echo "🚀 Ready to push! Run:" -echo " git push origin main" \ No newline at end of file diff --git a/core/path_mapper.py b/core/path_mapper.py index 6a46c9e..ab5d9f5 100644 --- a/core/path_mapper.py +++ b/core/path_mapper.py @@ -1,285 +1,35 @@ #!/usr/bin/env python3 """ -Path mapping module for NFOGuard -Handles path translation between container, Radarr/Sonarr, and download client paths +Path mapping utilities for NFOGuard +Handles conversion between external service paths and container paths """ -import os import re from pathlib import Path -from datetime import datetime -from typing import Dict, List, Optional, Tuple - -from core.logging import _log class PathMapper: - """Handles path mapping between different contexts (container, Radarr/Sonarr, downloads)""" + """Handles path mapping between different environments""" def __init__(self): - # Load path mappings from environment - self.container_to_radarr_movie_paths = self._load_movie_path_mappings() - self.container_to_sonarr_tv_paths = self._load_tv_path_mappings() - self.download_path_indicators = self._load_download_indicators() - - # Reverse mappings for efficiency - self.radarr_to_container_movie_paths = {v: k for k, v in self.container_to_radarr_movie_paths.items()} - self.sonarr_to_container_tv_paths = {v: k for k, v in self.container_to_sonarr_tv_paths.items()} - - def _load_movie_path_mappings(self) -> Dict[str, str]: - """Load movie path mappings from environment""" - # Container paths (what NFOGuard sees) - container_paths_str = os.environ.get("MOVIE_PATHS", "/media/movies,/media/movies6") - container_paths = [p.strip() for p in container_paths_str.split(",") if p.strip()] - - # Radarr paths (what Radarr sees) - radarr_paths_str = os.environ.get("RADARR_ROOT_FOLDERS", "/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6") - radarr_paths = [p.strip() for p in radarr_paths_str.split(",") if p.strip()] - - # Create mappings (container -> radarr) - mappings = {} - for i, container_path in enumerate(container_paths): - if i < len(radarr_paths): - mappings[container_path] = radarr_paths[i] - else: - # Default mapping if not enough Radarr paths specified - _log("WARNING", f"No Radarr path mapping for container path: {container_path}") - mappings[container_path] = container_path - - _log("INFO", f"Movie path mappings: {mappings}") - return mappings - - def _load_tv_path_mappings(self) -> Dict[str, str]: - """Load TV path mappings from environment""" - # Container paths - container_paths_str = os.environ.get("TV_PATHS", "/media/tv,/media/tv6") - container_paths = [p.strip() for p in container_paths_str.split(",") if p.strip()] - - # Sonarr paths - sonarr_paths_str = os.environ.get("SONARR_ROOT_FOLDERS", "/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6") - sonarr_paths = [p.strip() for p in sonarr_paths_str.split(",") if p.strip()] - - # Create mappings - mappings = {} - for i, container_path in enumerate(container_paths): - if i < len(sonarr_paths): - mappings[container_path] = sonarr_paths[i] - else: - _log("WARNING", f"No Sonarr path mapping for container path: {container_path}") - mappings[container_path] = container_path - - _log("INFO", f"TV path mappings: {mappings}") - return mappings - - def _load_download_indicators(self) -> List[str]: - """Load download path indicators from environment""" - default_indicators = [ - # nzbget paths - '/downloads/', '/download/', '/completed/', '/nzb-complete/', '/complete/', - # Transmission/Deluge - '/torrents/', '/torrent/', '/seed/', '/seeding/', - # SABnzbd - '/sabnzbd/', '/sab/', '/usenet/', - # qBittorrent - '/qbittorrent/', '/qbt/', - # Generic - '/importing/', '/processing/', '/temp/', '/tmp/', '/staging/', - # Client names - 'nzbget', 'sabnzbd', 'transmission', 'deluge', 'qbittorrent', 'rtorrent' - ] - - # Allow custom indicators from environment - custom_indicators_str = os.environ.get("DOWNLOAD_PATH_INDICATORS", "") - if custom_indicators_str: - custom_indicators = [p.strip() for p in custom_indicators_str.split(",") if p.strip()] - default_indicators.extend(custom_indicators) - - _log("DEBUG", f"Download path indicators: {default_indicators}") - return default_indicators - - def container_path_to_radarr_path(self, container_path: str) -> str: - """Convert container path to Radarr path""" - container_path = str(container_path).rstrip("/") - - for container_root, radarr_root in self.container_to_radarr_movie_paths.items(): - container_root = container_root.rstrip("/") - if container_path.startswith(container_root): - # Replace container root with Radarr root - relative_path = container_path[len(container_root):].lstrip("/") - radarr_path = f"{radarr_root.rstrip('/')}/{relative_path}" if relative_path else radarr_root.rstrip("/") - _log("DEBUG", f"Mapped container path {container_path} -> {radarr_path}") - return radarr_path - - _log("WARNING", f"No Radarr mapping found for container path: {container_path}") - return container_path - - def radarr_path_to_container_path(self, radarr_path: str) -> str: - """Convert Radarr path to container path""" - radarr_path = str(radarr_path).rstrip("/") - - for radarr_root, container_root in self.radarr_to_container_movie_paths.items(): - radarr_root = radarr_root.rstrip("/") - if radarr_path.startswith(radarr_root): - # Replace Radarr root with container root - 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}") - return radarr_path - - def container_path_to_sonarr_path(self, container_path: str) -> str: - """Convert container path to Sonarr path""" - container_path = str(container_path).rstrip("/") - - for container_root, sonarr_root in self.container_to_sonarr_tv_paths.items(): - container_root = container_root.rstrip("/") - if container_path.startswith(container_root): - relative_path = container_path[len(container_root):].lstrip("/") - sonarr_path = f"{sonarr_root.rstrip('/')}/{relative_path}" if relative_path else sonarr_root.rstrip("/") - _log("DEBUG", f"Mapped container path {container_path} -> {sonarr_path}") - return sonarr_path - - _log("WARNING", f"No Sonarr mapping found for container path: {container_path}") - return container_path - + pass + def sonarr_path_to_container_path(self, sonarr_path: str) -> str: """Convert Sonarr path to container path""" - sonarr_path = str(sonarr_path).rstrip("/") - - for sonarr_root, container_root in self.sonarr_to_container_tv_paths.items(): - sonarr_root = sonarr_root.rstrip("/") - if sonarr_path.startswith(sonarr_root): - relative_path = sonarr_path[len(sonarr_root):].lstrip("/") - container_path = f"{container_root.rstrip('/')}/{relative_path}" if relative_path else container_root.rstrip("/") - _log("DEBUG", f"Mapped Sonarr path {sonarr_path} -> {container_path}") - return container_path - - _log("WARNING", f"No container mapping found for Sonarr path: {sonarr_path}") + # Replace /mnt/unionfs/Media with /media + if sonarr_path.startswith("/mnt/unionfs/Media"): + return sonarr_path.replace("/mnt/unionfs/Media", "/media") return sonarr_path - - def is_download_path(self, path: str) -> bool: - """Check if path indicates a download/temporary location""" - path_lower = str(path).lower() - return any(indicator in path_lower for indicator in self.download_path_indicators) - - def analyze_import_source_path(self, source_path: str) -> Tuple[bool, str]: - """ - Analyze import source path to determine if it's from downloads. - - Returns: - (is_from_downloads, reason) - """ - if not source_path: - return False, "no_source_path" - - path_lower = str(source_path).lower() - - # Extract potential IMDb ID from path - imdb_match = re.search(r'(?:imdb-|tt)(\d{6,8})', path_lower) - if imdb_match: - imdb_id = f"tt{imdb_match.group(1)}" - _log("DEBUG", f"Found IMDb ID in path: {imdb_id}") - return True, f"imdb_in_path({imdb_id})" - - # Check for download indicators - for indicator in self.download_path_indicators: - if indicator in path_lower: - return True, f"download_indicator({indicator})" - - # Check if path is outside media roots (likely a download) - media_roots = [] - media_roots.extend(self.container_to_radarr_movie_paths.values()) - media_roots.extend(self.container_to_sonarr_tv_paths.values()) - - is_in_media = any(source_path.startswith(root.rstrip('/')) for root in media_roots) - if not is_in_media: - return True, "outside_media_roots" - - return False, "appears_to_be_media_path" - - def find_container_path_from_webhook(self, webhook_path: str, media_type: str = "movie") -> Optional[Path]: - """ - Find container path from webhook path information. - - Args: - webhook_path: Path from Radarr/Sonarr webhook - media_type: "movie" or "tv" - """ - if not webhook_path: - return None - - if media_type == "movie": - container_path = self.radarr_path_to_container_path(webhook_path) - else: - container_path = self.sonarr_path_to_container_path(webhook_path) - - path_obj = Path(container_path) - if path_obj.exists(): - _log("INFO", f"Found container path: {path_obj}") - return path_obj - - _log("WARNING", f"Container path does not exist: {path_obj}") - return None - - def get_debug_info(self) -> Dict[str, any]: - """Get debug information about path mappings""" - return { - "movie_mappings": { - "container_to_radarr": self.container_to_radarr_movie_paths, - "radarr_to_container": self.radarr_to_container_movie_paths - }, - "tv_mappings": { - "container_to_sonarr": self.container_to_sonarr_tv_paths, - "sonarr_to_container": self.sonarr_to_container_tv_paths - }, - "download_indicators": self.download_path_indicators - } - - -# Global instance -path_mapper = PathMapper() - - -if __name__ == "__main__": - # Test path mapping - import os - from datetime import datetime - # Set up test environment - os.environ["MOVIE_PATHS"] = "/media/movies,/media/movies6" - os.environ["RADARR_ROOT_FOLDERS"] = "/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6" - os.environ["TV_PATHS"] = "/media/tv,/media/tv6" - os.environ["SONARR_ROOT_FOLDERS"] = "/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6" + def radarr_path_to_container_path(self, radarr_path: str) -> str: + """Convert Radarr path to container path""" + # Replace /mnt/unionfs/Media with /media + if radarr_path.startswith("/mnt/unionfs/Media"): + container_path = radarr_path.replace("/mnt/unionfs/Media", "/media") + # Keep movies6 as movies6, don't convert to movies/6 + return container_path + return radarr_path - mapper = PathMapper() - - # Test movie path mapping - container_movie = "/media/movies/Test Movie [imdb-tt1234567]" - radarr_movie = mapper.container_path_to_radarr_path(container_movie) - print(f"Container -> Radarr: {container_movie} -> {radarr_movie}") - - back_to_container = mapper.radarr_path_to_container_path(radarr_movie) - print(f"Radarr -> Container: {radarr_movie} -> {back_to_container}") - - # Test download path detection - test_paths = [ - "/downloads/complete/Test.Movie.2023.1080p.BluRay.x264", - "/mnt/unionfs/Media/Movies/movies/Test Movie [imdb-tt1234567]", - "/nzbget/complete/Test.Series.S01E01.1080p.WEB.x264", - "/torrents/seed/Test.Movie.2023" - ] - - for path in test_paths: - is_download, reason = mapper.analyze_import_source_path(path) - print(f"Path analysis: {path} -> is_download={is_download} ({reason})") - - # Show debug info - debug_info = mapper.get_debug_info() - print(f"\nDebug info: {debug_info}") \ No newline at end of file + def container_path_to_host_path(self, container_path: str) -> str: + """Convert container path back to host path if needed""" + # This might be needed for file operations + return container_path \ No newline at end of file diff --git a/run_commit.sh b/run_commit.sh deleted file mode 100644 index a3d83e7..0000000 --- a/run_commit.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/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