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.
This commit is contained in:
+115
@@ -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
|
||||
@@ -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"
|
||||
@@ -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}")
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
+46
-43
@@ -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}")
|
||||
|
||||
# Add to batch queue
|
||||
webhook_dict = {
|
||||
'path': str(movie_path),
|
||||
'movie_info': movie_info,
|
||||
'event_type': webhook.eventType
|
||||
# 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}")
|
||||
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user