push updates to paths again
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
# MEDIA PATHS (REQUIRED) - FIXED
|
# MEDIA PATHS (REQUIRED) - FIXED
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Container paths (what NFOGuard sees inside container)
|
# 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
|
TV_PATHS=/media/TV/tv,/media/TV/tv6
|
||||||
|
|
||||||
# Radarr paths (what Radarr sees on the host system)
|
# Radarr paths (what Radarr sees on the host system)
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
+20
-270
@@ -1,285 +1,35 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Path mapping module for NFOGuard
|
Path mapping utilities for NFOGuard
|
||||||
Handles path translation between container, Radarr/Sonarr, and download client paths
|
Handles conversion between external service paths and container paths
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
from core.logging import _log
|
|
||||||
|
|
||||||
|
|
||||||
class PathMapper:
|
class PathMapper:
|
||||||
"""Handles path mapping between different contexts (container, Radarr/Sonarr, downloads)"""
|
"""Handles path mapping between different environments"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Load path mappings from environment
|
pass
|
||||||
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
|
|
||||||
|
|
||||||
def sonarr_path_to_container_path(self, sonarr_path: str) -> str:
|
def sonarr_path_to_container_path(self, sonarr_path: str) -> str:
|
||||||
"""Convert Sonarr path to container path"""
|
"""Convert Sonarr path to container path"""
|
||||||
sonarr_path = str(sonarr_path).rstrip("/")
|
# Replace /mnt/unionfs/Media with /media
|
||||||
|
if sonarr_path.startswith("/mnt/unionfs/Media"):
|
||||||
for sonarr_root, container_root in self.sonarr_to_container_tv_paths.items():
|
return sonarr_path.replace("/mnt/unionfs/Media", "/media")
|
||||||
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}")
|
|
||||||
return sonarr_path
|
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
|
def radarr_path_to_container_path(self, radarr_path: str) -> str:
|
||||||
os.environ["MOVIE_PATHS"] = "/media/movies,/media/movies6"
|
"""Convert Radarr path to container path"""
|
||||||
os.environ["RADARR_ROOT_FOLDERS"] = "/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6"
|
# Replace /mnt/unionfs/Media with /media
|
||||||
os.environ["TV_PATHS"] = "/media/tv,/media/tv6"
|
if radarr_path.startswith("/mnt/unionfs/Media"):
|
||||||
os.environ["SONARR_ROOT_FOLDERS"] = "/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6"
|
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()
|
def container_path_to_host_path(self, container_path: str) -> str:
|
||||||
|
"""Convert container path back to host path if needed"""
|
||||||
# Test movie path mapping
|
# This might be needed for file operations
|
||||||
container_movie = "/media/movies/Test Movie [imdb-tt1234567]"
|
return container_path
|
||||||
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}")
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user