db updates
This commit is contained in:
@@ -1,74 +0,0 @@
|
|||||||
# =================================
|
|
||||||
# NFOGuard - Simple Configuration
|
|
||||||
# =================================
|
|
||||||
|
|
||||||
# --- Required: Media Library Paths ---
|
|
||||||
# Container paths (what NFOGuard sees)
|
|
||||||
TV_PATHS=/media/tv,/media/tv6
|
|
||||||
MOVIE_PATHS=/media/movies,/media/movies6
|
|
||||||
|
|
||||||
# --- Required: Sonarr/Radarr Connection ---
|
|
||||||
SONARR_URL=http://sonarr:8989
|
|
||||||
SONARR_API_KEY=
|
|
||||||
|
|
||||||
RADARR_URL=http://radarr:7878
|
|
||||||
RADARR_API_KEY=
|
|
||||||
|
|
||||||
# --- Optional: Direct Radarr Database Access (High Performance) ---
|
|
||||||
# Enable direct database access for faster import date queries
|
|
||||||
# Supports both SQLite and PostgreSQL
|
|
||||||
RADARR_DB_TYPE=postgresql
|
|
||||||
# For PostgreSQL (recommended for production):
|
|
||||||
RADARR_DB_HOST=postgres_host
|
|
||||||
RADARR_DB_PORT=5432
|
|
||||||
RADARR_DB_NAME=radarr-main
|
|
||||||
RADARR_DB_USER=postgres
|
|
||||||
RADARR_DB_PASSWORD=your_password
|
|
||||||
# Alternative: use connection string
|
|
||||||
#RADARR_DB_URL=postgresql://user:pass@host:5432/dbname
|
|
||||||
|
|
||||||
# For SQLite (if using default Radarr setup):
|
|
||||||
#RADARR_DB_TYPE=sqlite
|
|
||||||
#RADARR_DB_PATH=/config/radarr.db
|
|
||||||
|
|
||||||
# --- Required: TMDB for Release Dates ---
|
|
||||||
# Get free API key from https://www.themoviedb.org/settings/api
|
|
||||||
TMDB_API_KEY=
|
|
||||||
TMDB_COUNTRY=US
|
|
||||||
|
|
||||||
# --- Behavior Settings ---
|
|
||||||
# Movie processing strategy: import_then_digital, digital_then_import
|
|
||||||
MOVIE_PRIORITY=import_then_digital
|
|
||||||
|
|
||||||
# When to query APIs: always, if_missing, never
|
|
||||||
MOVIE_POLL_MODE=always
|
|
||||||
|
|
||||||
# Update mode: backfill_only, overwrite
|
|
||||||
MOVIE_DATE_UPDATE_MODE=backfill_only
|
|
||||||
|
|
||||||
# Lock metadata in NFO files to prevent overwrites: true, false
|
|
||||||
LOCK_METADATA=true
|
|
||||||
|
|
||||||
# Brand name in NFO comments
|
|
||||||
MANAGER_BRAND=NFOGuard
|
|
||||||
|
|
||||||
# --- CRITICAL: Path Mapping ---
|
|
||||||
# What Radarr/Sonarr see vs what container sees
|
|
||||||
RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6
|
|
||||||
SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6
|
|
||||||
|
|
||||||
# Download path indicators (for nzbget, etc.)
|
|
||||||
DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/sabnzbd/,/completed/,/torrents/
|
|
||||||
|
|
||||||
# --- Optional: Additional Digital Release Sources ---
|
|
||||||
#OMDB_API_KEY=your_omdb_api_key_here
|
|
||||||
JELLYSEERR_URL=http://jellyseerr:5055
|
|
||||||
JELLYSEERR_API_KEY=
|
|
||||||
|
|
||||||
# --- Optional: Advanced Settings ---
|
|
||||||
BATCH_DELAY=5.0
|
|
||||||
MAX_CONCURRENT_SERIES=3
|
|
||||||
TIMEOUT_SECONDS=45
|
|
||||||
DEBUG=false
|
|
||||||
DB_PATH=/app/data/media_dates.db
|
|
||||||
PORT=8080
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# NFOGuard Environment Configuration Template
|
||||||
|
# Copy this to .env and customize for your setup
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# MEDIA PATHS (REQUIRED)
|
||||||
|
# ===========================================
|
||||||
|
# Paths where your movies and TV shows are stored inside the container
|
||||||
|
# Adjust these to match YOUR directory structure
|
||||||
|
TV_PATHS=/media/TV/tv,/media/TV/tv6
|
||||||
|
MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# DATABASE CONFIGURATION
|
||||||
|
# ===========================================
|
||||||
|
# NFOGuard SQLite database location
|
||||||
|
DB_PATH=/app/data/media_dates.db
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# RADARR DATABASE CONNECTION (REQUIRED)
|
||||||
|
# ===========================================
|
||||||
|
# Connection to Radarr's PostgreSQL database
|
||||||
|
RADARR_DB_HOST=radarr-postgres
|
||||||
|
RADARR_DB_PORT=5432
|
||||||
|
RADARR_DB_NAME=radarr
|
||||||
|
RADARR_DB_USER=radarr
|
||||||
|
RADARR_DB_PASSWORD=your_radarr_db_password
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# RADARR API (OPTIONAL - for fallback)
|
||||||
|
# ===========================================
|
||||||
|
# Only needed if you want API fallback (not recommended for performance)
|
||||||
|
RADARR_URL=http://radarr:7878
|
||||||
|
RADARR_API_KEY=your_radarr_api_key
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# SONARR API (OPTIONAL)
|
||||||
|
# ===========================================
|
||||||
|
# Only needed for TV processing
|
||||||
|
SONARR_URL=http://sonarr:8989
|
||||||
|
SONARR_API_KEY=your_sonarr_api_key
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# NFO MANAGEMENT
|
||||||
|
# ===========================================
|
||||||
|
# Whether to create/update .nfo files
|
||||||
|
MANAGE_NFO=true
|
||||||
|
|
||||||
|
# Whether to fix file modification times to match import dates
|
||||||
|
FIX_DIR_MTIMES=true
|
||||||
|
|
||||||
|
# Whether to add <lockdata> tags to prevent metadata drift
|
||||||
|
LOCK_METADATA=true
|
||||||
|
|
||||||
|
# Brand name to add to NFO files
|
||||||
|
MANAGER_BRAND=NFOGuard
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# PROCESSING SETTINGS
|
||||||
|
# ===========================================
|
||||||
|
# Delay before processing batched webhook events (seconds)
|
||||||
|
BATCH_DELAY=5.0
|
||||||
|
|
||||||
|
# Maximum concurrent series processing
|
||||||
|
MAX_CONCURRENT_SERIES=3
|
||||||
|
|
||||||
|
# Movie date strategy: import_then_digital or digital_then_import
|
||||||
|
MOVIE_PRIORITY=import_then_digital
|
||||||
|
|
||||||
|
# When to query APIs: always, if_missing, never
|
||||||
|
MOVIE_POLL_MODE=always
|
||||||
|
|
||||||
|
# Update mode: backfill_only or overwrite
|
||||||
|
MOVIE_DATE_UPDATE_MODE=backfill_only
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# LOGGING
|
||||||
|
# ===========================================
|
||||||
|
# Enable debug logging
|
||||||
|
DEBUG=false
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# SERVER
|
||||||
|
# ===========================================
|
||||||
|
# Port to run the webhook server on
|
||||||
|
PORT=8080
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
# NFOGuard Deployment Guide
|
||||||
|
|
||||||
|
## 🚀 Quick Start for Docker Deployment
|
||||||
|
|
||||||
|
### Step 1: Environment Configuration
|
||||||
|
```bash
|
||||||
|
# Copy the environment template
|
||||||
|
cp .env.template .env
|
||||||
|
|
||||||
|
# Edit with your specific paths and database credentials
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key variables to customize in `.env`**:
|
||||||
|
```bash
|
||||||
|
# YOUR MEDIA PATHS (adjust these to your setup)
|
||||||
|
TV_PATHS=/media/TV/tv,/media/TV/tv6
|
||||||
|
MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
|
||||||
|
HOST_MEDIA_PATH=/mnt/unionfs/Media
|
||||||
|
|
||||||
|
# YOUR RADARR DATABASE PASSWORD
|
||||||
|
RADARR_DB_PASSWORD=your_actual_password_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Deploy with Docker Compose
|
||||||
|
```bash
|
||||||
|
# Start NFOGuard
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker-compose logs -f nfoguard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Test Database Connection
|
||||||
|
```bash
|
||||||
|
# Test Radarr database connectivity
|
||||||
|
curl -X POST "http://localhost:8080/test/bulk-update"
|
||||||
|
|
||||||
|
# Expected response:
|
||||||
|
# {"status": "success", "movies_with_imdb": 1500, "database_type": "postgresql"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Test Movie Discovery
|
||||||
|
```bash
|
||||||
|
# Test that your movie paths are correctly configured
|
||||||
|
curl -X POST "http://localhost:8080/test/movie-scan"
|
||||||
|
|
||||||
|
# Expected response:
|
||||||
|
# {"status": "success", "message": "Movie scan test found 1500 movies",
|
||||||
|
# "path_results": [{"path": "/media/Movies/movies", "exists": true, "movies_found": 800}, ...]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Validate Database Performance
|
||||||
|
```bash
|
||||||
|
# Test the optimized database query performance
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1674782"
|
||||||
|
|
||||||
|
# Expected response with July 2025 date:
|
||||||
|
# {"detected_import_date": "2025-07-08T03:30:04+00:00", "import_source": "radarr:history.import"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Production Workflow
|
||||||
|
|
||||||
|
### Manual Operations (via webhooks)
|
||||||
|
|
||||||
|
**1. Manual Movie Scan**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Bulk Update All Movies**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/bulk/update"
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. System Health Check**
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/health"
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Database Statistics**
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/stats"
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Debug Specific Movie**
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1674782"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Radarr Webhook Integration
|
||||||
|
|
||||||
|
**Add to Radarr → Settings → Connect → Webhook:**
|
||||||
|
- **Name**: NFOGuard
|
||||||
|
- **URL**: `http://nfoguard:8080/webhook/radarr`
|
||||||
|
- **Method**: POST
|
||||||
|
- **Events**: On Download, On Upgrade, On Rename
|
||||||
|
|
||||||
|
NFOGuard will automatically process movies when Radarr sends webhooks.
|
||||||
|
|
||||||
|
## 🔧 Environment Variables Reference
|
||||||
|
|
||||||
|
### Required Variables
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `MOVIE_PATHS` | Comma-separated movie directories | `/media/Movies/movies,/media/Movies/movies6` |
|
||||||
|
| `TV_PATHS` | Comma-separated TV directories | `/media/TV/tv,/media/TV/tv6` |
|
||||||
|
| `RADARR_DB_HOST` | Radarr PostgreSQL host | `radarr-postgres` |
|
||||||
|
| `RADARR_DB_PASSWORD` | Radarr database password | `your_password` |
|
||||||
|
|
||||||
|
### Optional Variables
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `HOST_MEDIA_PATH` | `/mnt/unionfs/Media` | Host path mounted to `/media` |
|
||||||
|
| `DB_PATH` | `/app/data/media_dates.db` | NFOGuard database location |
|
||||||
|
| `MANAGE_NFO` | `true` | Create/update .nfo files |
|
||||||
|
| `FIX_DIR_MTIMES` | `true` | Fix file modification times |
|
||||||
|
| `MOVIE_PRIORITY` | `import_then_digital` | Date preference strategy |
|
||||||
|
| `DEBUG` | `false` | Enable debug logging |
|
||||||
|
|
||||||
|
## 🏗 Docker Compose Customization
|
||||||
|
|
||||||
|
**For different media structures:**
|
||||||
|
```yaml
|
||||||
|
# Example: Different media structure
|
||||||
|
services:
|
||||||
|
nfoguard:
|
||||||
|
environment:
|
||||||
|
- TV_PATHS=/storage/tv_shows,/storage/anime
|
||||||
|
- MOVIE_PATHS=/storage/movies
|
||||||
|
volumes:
|
||||||
|
- "/storage:/storage:rw"
|
||||||
|
- "./data:/app/data"
|
||||||
|
```
|
||||||
|
|
||||||
|
**For external database networks:**
|
||||||
|
```yaml
|
||||||
|
# Example: Connect to existing Radarr network
|
||||||
|
services:
|
||||||
|
nfoguard:
|
||||||
|
networks:
|
||||||
|
- radarr_network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
radarr_network:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠 Troubleshooting
|
||||||
|
|
||||||
|
### "0 movies processed"
|
||||||
|
1. Check paths: `curl -X POST "http://localhost:8080/test/movie-scan"`
|
||||||
|
2. Verify environment variables in `.env`
|
||||||
|
3. Ensure directories exist and have IMDb tags: `Movie Name [imdb-tt1234567] (2023)`
|
||||||
|
|
||||||
|
### Database connection errors
|
||||||
|
1. Test connection: `curl -X POST "http://localhost:8080/test/bulk-update"`
|
||||||
|
2. Verify `RADARR_DB_*` variables
|
||||||
|
3. Check Docker network connectivity
|
||||||
|
|
||||||
|
### Performance issues
|
||||||
|
1. Check health: `curl "http://localhost:8080/health"`
|
||||||
|
2. Monitor logs: `docker-compose logs -f nfoguard`
|
||||||
|
3. Verify database query performance: `curl "http://localhost:8080/debug/movie/tt1674782"`
|
||||||
|
|
||||||
|
## 📈 Monitoring
|
||||||
|
|
||||||
|
**Health Check Endpoint**: `GET /health`
|
||||||
|
- Server status and uptime
|
||||||
|
- Database connectivity
|
||||||
|
- Radarr database status
|
||||||
|
|
||||||
|
**Statistics Endpoint**: `GET /stats`
|
||||||
|
- Movie/TV counts
|
||||||
|
- Processing statistics
|
||||||
|
|
||||||
|
**Batch Queue Status**: `GET /batch/status`
|
||||||
|
- Pending webhook events
|
||||||
|
- Processing queue status
|
||||||
|
|
||||||
|
This deployment approach ensures NFOGuard can be easily configured for any media server setup while maintaining the database-only performance optimizations.
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
# NFOGuard Testing Guide
|
||||||
|
|
||||||
|
## 🧪 Test Scripts Overview
|
||||||
|
|
||||||
|
NFOGuard includes 3 specialized testing scripts to validate different parts of the workflow. These are designed to be run via webhooks or manual API calls, not directly on the command line.
|
||||||
|
|
||||||
|
### 1. `test_bulk_update.py` - Database Connection Validator
|
||||||
|
**Purpose**: Tests that the bulk update system can connect to databases without modifying any data.
|
||||||
|
|
||||||
|
**What it tests**:
|
||||||
|
- Radarr database connection
|
||||||
|
- NFOGuard database connection
|
||||||
|
- Query execution (reads first 5 movies)
|
||||||
|
- Import detection logic
|
||||||
|
|
||||||
|
**When to use**: Before running the full bulk update to ensure everything is configured correctly.
|
||||||
|
|
||||||
|
**How to run**:
|
||||||
|
```bash
|
||||||
|
# Via webhook/API inside container
|
||||||
|
curl -X POST "http://nfoguard:8080/test/bulk-update"
|
||||||
|
|
||||||
|
# Or manually inside container
|
||||||
|
python3 test_bulk_update.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `test_movie_scan.py` - Directory Structure Validator
|
||||||
|
**Purpose**: Tests the movie directory scanning logic to ensure movies will be found.
|
||||||
|
|
||||||
|
**What it tests**:
|
||||||
|
- Path configuration validation
|
||||||
|
- IMDb ID parsing from directory names
|
||||||
|
- Movie discovery logic
|
||||||
|
- Directory structure understanding
|
||||||
|
|
||||||
|
**When to use**: When manual scans report "0 movies found" to debug path issues.
|
||||||
|
|
||||||
|
**Sample output**:
|
||||||
|
```
|
||||||
|
🔍 Testing Movie Directory Scanning
|
||||||
|
✅ '/media/Movies/movies' -> Found 150 movies with IMDb IDs
|
||||||
|
✅ 'The Dark Knight [imdb-tt0468569] (2008)' -> tt0468569
|
||||||
|
❌ 'Invalid Movie (2020)' -> No IMDb ID found
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `test_end_to_end.py` - Complete Workflow Validator
|
||||||
|
**Purpose**: Tests the entire NFOGuard workflow from database queries to NFO creation.
|
||||||
|
|
||||||
|
**What it tests**:
|
||||||
|
- Server health and connectivity
|
||||||
|
- Database performance (your July 2025 date test)
|
||||||
|
- Manual scan trigger
|
||||||
|
- Batch processing status
|
||||||
|
- Statistics endpoints
|
||||||
|
|
||||||
|
**When to use**: After deployment to verify the complete system works end-to-end.
|
||||||
|
|
||||||
|
**How to run**:
|
||||||
|
```bash
|
||||||
|
# Via webhook inside container
|
||||||
|
curl -X POST "http://nfoguard:8080/test/end-to-end"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Testing Workflow for Docker Deployment
|
||||||
|
|
||||||
|
### Step 1: Deploy with Environment Variables
|
||||||
|
```bash
|
||||||
|
# Copy your environment template
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your actual database password
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Validate Database Connections
|
||||||
|
```bash
|
||||||
|
# Test database connectivity
|
||||||
|
curl -X POST "http://localhost:8080/test/bulk-update"
|
||||||
|
|
||||||
|
# Should show:
|
||||||
|
# ✅ Radarr database connection successful
|
||||||
|
# ✅ NFOGuard database connection successful
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Test Movie Discovery
|
||||||
|
```bash
|
||||||
|
# Test that movies will be found
|
||||||
|
curl -X POST "http://localhost:8080/test/movie-scan"
|
||||||
|
|
||||||
|
# Should show paths and sample movies found
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Run Full End-to-End Test
|
||||||
|
```bash
|
||||||
|
# Complete workflow test
|
||||||
|
curl -X POST "http://localhost:8080/test/end-to-end"
|
||||||
|
|
||||||
|
# Should show all systems operational
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Production Testing
|
||||||
|
```bash
|
||||||
|
# Test database performance (your working July date)
|
||||||
|
curl "http://localhost:8080/debug/movie/tt1674782"
|
||||||
|
|
||||||
|
# Should return: 2025-07-08T03:30:04+00:00
|
||||||
|
|
||||||
|
# Run manual scan
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"
|
||||||
|
|
||||||
|
# Run bulk update
|
||||||
|
curl -X POST "http://localhost:8080/bulk/update"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠 Why These Tests Are Needed
|
||||||
|
|
||||||
|
1. **Docker Environment Isolation**: Tests run in the same environment as the production app
|
||||||
|
2. **Database-Only Validation**: Ensures the optimized database queries work correctly
|
||||||
|
3. **Path Configuration**: Validates that your specific directory structure is properly configured
|
||||||
|
4. **Webhook Integration**: Tests run via HTTP calls, same as Radarr webhooks
|
||||||
|
5. **CI/CD Integration**: Can be automated in deployment pipelines
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting with Tests
|
||||||
|
|
||||||
|
**"0 movies processed"** → Run `test_movie_scan.py` to check path configuration
|
||||||
|
**Database errors** → Run `test_bulk_update.py` to validate connections
|
||||||
|
**Workflow issues** → Run `test_end_to_end.py` for comprehensive diagnosis
|
||||||
|
|
||||||
|
These tests are designed to catch issues before they impact your media processing workflow.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
nfoguard:
|
||||||
|
build: .
|
||||||
|
container_name: nfoguard
|
||||||
|
ports:
|
||||||
|
- "${PORT:-8080}:8080"
|
||||||
|
volumes:
|
||||||
|
# Mount your media directories (adjust these paths to match your setup)
|
||||||
|
- "${HOST_MEDIA_PATH:-/mnt/unionfs/Media}:/media:rw"
|
||||||
|
|
||||||
|
# NFOGuard data directory for database and logs
|
||||||
|
- "./data:/app/data"
|
||||||
|
|
||||||
|
# Optional: Mount config if you want to override
|
||||||
|
# - "./config:/app/config"
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# Media paths inside container - customize these in your .env file
|
||||||
|
- TV_PATHS=${TV_PATHS:-/media/tv,/media/tv6}
|
||||||
|
- MOVIE_PATHS=${MOVIE_PATHS:-/media/movies,/media/movies6}
|
||||||
|
|
||||||
|
# Database
|
||||||
|
- DB_PATH=${DB_PATH:-/app/data/media_dates.db}
|
||||||
|
|
||||||
|
# Radarr Database (REQUIRED for database-only mode)
|
||||||
|
- RADARR_DB_HOST=${RADARR_DB_HOST}
|
||||||
|
- RADARR_DB_PORT=${RADARR_DB_PORT:-5432}
|
||||||
|
- RADARR_DB_NAME=${RADARR_DB_NAME:-radarr}
|
||||||
|
- RADARR_DB_USER=${RADARR_DB_USER}
|
||||||
|
- RADARR_DB_PASSWORD=${RADARR_DB_PASSWORD}
|
||||||
|
|
||||||
|
# Optional Radarr API (fallback only)
|
||||||
|
- RADARR_URL=${RADARR_URL:-}
|
||||||
|
- RADARR_API_KEY=${RADARR_API_KEY:-}
|
||||||
|
|
||||||
|
# Optional Sonarr API (for TV processing)
|
||||||
|
- SONARR_URL=${SONARR_URL:-}
|
||||||
|
- SONARR_API_KEY=${SONARR_API_KEY:-}
|
||||||
|
|
||||||
|
# NFO Management
|
||||||
|
- MANAGE_NFO=${MANAGE_NFO:-true}
|
||||||
|
- FIX_DIR_MTIMES=${FIX_DIR_MTIMES:-true}
|
||||||
|
- LOCK_METADATA=${LOCK_METADATA:-true}
|
||||||
|
- MANAGER_BRAND=${MANAGER_BRAND:-NFOGuard}
|
||||||
|
|
||||||
|
# Processing settings
|
||||||
|
- BATCH_DELAY=${BATCH_DELAY:-5.0}
|
||||||
|
- MAX_CONCURRENT_SERIES=${MAX_CONCURRENT_SERIES:-3}
|
||||||
|
- MOVIE_PRIORITY=${MOVIE_PRIORITY:-import_then_digital}
|
||||||
|
- MOVIE_POLL_MODE=${MOVIE_POLL_MODE:-always}
|
||||||
|
- MOVIE_DATE_UPDATE_MODE=${MOVIE_DATE_UPDATE_MODE:-backfill_only}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
- DEBUG=${DEBUG:-false}
|
||||||
|
|
||||||
|
# Server
|
||||||
|
- PORT=${PORT:-8080}
|
||||||
|
|
||||||
|
# Optional: Connect to Radarr's database network
|
||||||
|
# networks:
|
||||||
|
# - radarr_network
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# Optional: If you need to connect to Radarr's database network
|
||||||
|
# networks:
|
||||||
|
# radarr_network:
|
||||||
|
# external: true
|
||||||
+69
@@ -1044,6 +1044,75 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
background_tasks.add_task(run_scan)
|
background_tasks.add_task(run_scan)
|
||||||
return {"status": "started", "message": f"Manual {scan_type} scan started"}
|
return {"status": "started", "message": f"Manual {scan_type} scan started"}
|
||||||
|
|
||||||
|
@app.post("/test/bulk-update")
|
||||||
|
async def test_bulk_update():
|
||||||
|
"""Test bulk update functionality without modifying data"""
|
||||||
|
try:
|
||||||
|
from clients.radarr_db_client import RadarrDbClient
|
||||||
|
|
||||||
|
# Test Radarr database
|
||||||
|
radarr_db = RadarrDbClient.from_env()
|
||||||
|
if not radarr_db:
|
||||||
|
return {"status": "error", "message": "Radarr database connection failed"}
|
||||||
|
|
||||||
|
# Test query execution
|
||||||
|
query = 'SELECT COUNT(*) FROM "Movies" m JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id" WHERE mm."ImdbId" IS NOT NULL'
|
||||||
|
with radarr_db._get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
movie_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Bulk update test passed",
|
||||||
|
"movies_with_imdb": movie_count,
|
||||||
|
"database_type": radarr_db.db_type
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"Bulk update test failed: {e}"}
|
||||||
|
|
||||||
|
@app.post("/test/movie-scan")
|
||||||
|
async def test_movie_scan():
|
||||||
|
"""Test movie directory scanning logic"""
|
||||||
|
try:
|
||||||
|
results = []
|
||||||
|
for path in config.movie_paths:
|
||||||
|
path_result = {
|
||||||
|
"path": str(path),
|
||||||
|
"exists": path.exists(),
|
||||||
|
"movies_found": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.exists():
|
||||||
|
for item in path.iterdir():
|
||||||
|
if item.is_dir() and nfo_manager.parse_imdb_from_path(item):
|
||||||
|
path_result["movies_found"] += 1
|
||||||
|
|
||||||
|
results.append(path_result)
|
||||||
|
|
||||||
|
total_movies = sum(r["movies_found"] for r in results)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": f"Movie scan test found {total_movies} movies",
|
||||||
|
"path_results": results
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"Movie scan test failed: {e}"}
|
||||||
|
|
||||||
|
@app.post("/bulk/update")
|
||||||
|
async def trigger_bulk_update(background_tasks: BackgroundTasks):
|
||||||
|
"""Trigger bulk update of all movies"""
|
||||||
|
async def run_bulk_update():
|
||||||
|
try:
|
||||||
|
from bulk_update_movies import bulk_update_all_movies
|
||||||
|
success = bulk_update_all_movies()
|
||||||
|
_log("INFO", f"Bulk update completed: {'success' if success else 'failed'}")
|
||||||
|
except Exception as e:
|
||||||
|
_log("ERROR", f"Bulk update error: {e}")
|
||||||
|
|
||||||
|
background_tasks.add_task(run_bulk_update)
|
||||||
|
return {"status": "started", "message": "Bulk update started"}
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Main
|
# Main
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for bulk_update_movies.py
|
||||||
|
This tests the bulk update functionality without modifying data
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def test_bulk_update_dry_run():
|
||||||
|
"""Test bulk update script in dry-run mode"""
|
||||||
|
print("🧪 Testing Bulk Update Script")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# Check if we can import the required modules
|
||||||
|
try:
|
||||||
|
from clients.radarr_db_client import RadarrDbClient
|
||||||
|
from core.database import NFOGuardDatabase
|
||||||
|
print("✅ Successfully imported required modules")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Import error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test database connections
|
||||||
|
print("\n🔌 Testing Database Connections...")
|
||||||
|
|
||||||
|
# Test Radarr database
|
||||||
|
try:
|
||||||
|
radarr_db = RadarrDbClient.from_env()
|
||||||
|
if not radarr_db:
|
||||||
|
print("❌ Radarr database connection failed - check environment variables")
|
||||||
|
return False
|
||||||
|
print("✅ Radarr database connection successful")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Radarr database error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test NFOGuard database
|
||||||
|
try:
|
||||||
|
db_path = Path(os.environ.get("DB_PATH", "/app/data/media_dates.db"))
|
||||||
|
nfo_db = NFOGuardDatabase(db_path)
|
||||||
|
print(f"✅ NFOGuard database connection successful: {db_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ NFOGuard database error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test query execution (dry run)
|
||||||
|
print("\n📊 Testing Query Execution...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = """
|
||||||
|
SELECT
|
||||||
|
mm."ImdbId" as imdb_id,
|
||||||
|
m."Id" as movie_id,
|
||||||
|
mm."Title" as title,
|
||||||
|
mm."Year" as year,
|
||||||
|
m."Path" as path
|
||||||
|
FROM "Movies" m
|
||||||
|
JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
|
||||||
|
WHERE mm."ImdbId" IS NOT NULL AND mm."ImdbId" != ''
|
||||||
|
ORDER BY mm."Title"
|
||||||
|
LIMIT 5
|
||||||
|
"""
|
||||||
|
|
||||||
|
with radarr_db._get_connection() as conn:
|
||||||
|
if radarr_db.db_type == "postgresql":
|
||||||
|
import psycopg2.extras
|
||||||
|
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||||
|
else:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
movies = cursor.fetchall()
|
||||||
|
|
||||||
|
print(f"✅ Successfully queried Radarr database: {len(movies)} sample movies found")
|
||||||
|
|
||||||
|
# Show sample data
|
||||||
|
for i, movie in enumerate(movies):
|
||||||
|
if radarr_db.db_type == "postgresql":
|
||||||
|
imdb_id, title, year = movie['imdb_id'], movie['title'], movie['year']
|
||||||
|
else:
|
||||||
|
imdb_id, title, year = movie[0], movie[2], movie[3]
|
||||||
|
print(f" Sample {i+1}: {title} ({year}) - {imdb_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Query execution failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("\n✅ All tests passed! Bulk update script should work correctly.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_bulk_update_dry_run()
|
||||||
|
if not success:
|
||||||
|
print("\n❌ Bulk update test failed!")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("\n🎉 Bulk update test successful!")
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
End-to-end testing script for NFOGuard workflow
|
||||||
|
Tests the complete flow: Database -> Manual Scan -> NFO Creation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def test_server_health():
|
||||||
|
"""Test if the server is running and healthy"""
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:8080/health", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
health_data = response.json()
|
||||||
|
print(f"✅ Server is healthy: {health_data['status']}")
|
||||||
|
print(f" Version: {health_data['version']}")
|
||||||
|
print(f" Database: {health_data['database_status']}")
|
||||||
|
if health_data.get('radarr_database'):
|
||||||
|
radarr_status = health_data['radarr_database']['status']
|
||||||
|
print(f" Radarr DB: {radarr_status}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Server health check failed: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Cannot connect to server: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_database_performance():
|
||||||
|
"""Test the database performance endpoint"""
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:8080/debug/movie/tt1674782", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
debug_data = response.json()
|
||||||
|
if debug_data.get('detected_import_date'):
|
||||||
|
print(f"✅ Database query works: {debug_data['detected_import_date']}")
|
||||||
|
print(f" Source: {debug_data['import_source']}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ No import date found for test movie")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"❌ Debug endpoint failed: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Database test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_manual_scan():
|
||||||
|
"""Test the manual scan functionality"""
|
||||||
|
try:
|
||||||
|
print("🔍 Starting manual movie scan...")
|
||||||
|
response = requests.post(
|
||||||
|
"http://localhost:8080/manual/scan?scan_type=movies",
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
scan_data = response.json()
|
||||||
|
print(f"✅ Manual scan started: {scan_data['message']}")
|
||||||
|
|
||||||
|
# Wait a bit for processing
|
||||||
|
print("⏳ Waiting for scan to process...")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
# Check batch status
|
||||||
|
batch_response = requests.get("http://localhost:8080/batch/status", timeout=10)
|
||||||
|
if batch_response.status_code == 200:
|
||||||
|
batch_data = batch_response.json()
|
||||||
|
print(f" Pending items: {batch_data['pending_count']}")
|
||||||
|
print(f" Processing items: {batch_data['processing_count']}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Manual scan failed: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Manual scan test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_stats_endpoint():
|
||||||
|
"""Test database stats"""
|
||||||
|
try:
|
||||||
|
response = requests.get("http://localhost:8080/stats", timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
stats = response.json()
|
||||||
|
print(f"✅ Database stats:")
|
||||||
|
print(f" Movies: {stats.get('movie_count', 0)}")
|
||||||
|
print(f" TV Series: {stats.get('series_count', 0)}")
|
||||||
|
print(f" Episodes: {stats.get('episode_count', 0)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Stats endpoint failed: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Stats test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run_end_to_end_test():
|
||||||
|
"""Run complete end-to-end test suite"""
|
||||||
|
print("🎯 NFOGuard End-to-End Testing")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
("Server Health", test_server_health),
|
||||||
|
("Database Performance", test_database_performance),
|
||||||
|
("Manual Scan", test_manual_scan),
|
||||||
|
("Database Stats", test_stats_endpoint)
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for test_name, test_func in tests:
|
||||||
|
print(f"\n🧪 Running: {test_name}")
|
||||||
|
try:
|
||||||
|
if test_func():
|
||||||
|
passed += 1
|
||||||
|
print(f"✅ {test_name} passed")
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
print(f"❌ {test_name} failed")
|
||||||
|
except Exception as e:
|
||||||
|
failed += 1
|
||||||
|
print(f"❌ {test_name} crashed: {e}")
|
||||||
|
|
||||||
|
print(f"\n📊 Test Results:")
|
||||||
|
print(f" ✅ Passed: {passed}")
|
||||||
|
print(f" ❌ Failed: {failed}")
|
||||||
|
print(f" 📈 Success Rate: {passed}/{passed+failed} ({100*passed/(passed+failed):.1f}%)")
|
||||||
|
|
||||||
|
return failed == 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = run_end_to_end_test()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 All end-to-end tests passed!")
|
||||||
|
print("\n📋 Next Steps:")
|
||||||
|
print("1. Run bulk update: python3 bulk_update_movies.py")
|
||||||
|
print("2. Test with real movie: curl http://localhost:8080/debug/movie/tt1674782")
|
||||||
|
print("3. Check NFO file creation in /media/Movies/movies/")
|
||||||
|
print("4. Verify file timestamps match import dates")
|
||||||
|
else:
|
||||||
|
print("\n💥 Some tests failed - check the logs above")
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify movie directory scanning logic
|
||||||
|
Simulates what the manual scan would do with correct paths
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def test_movie_scanning():
|
||||||
|
"""Test movie directory scanning logic"""
|
||||||
|
print("🔍 Testing Movie Directory Scanning")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Test paths - use the corrected paths from the code
|
||||||
|
movie_paths = [
|
||||||
|
Path("/media/Movies/movies"),
|
||||||
|
Path("/media/Movies/movies6")
|
||||||
|
]
|
||||||
|
|
||||||
|
print("📁 Testing paths:")
|
||||||
|
for path in movie_paths:
|
||||||
|
print(f" {path}")
|
||||||
|
|
||||||
|
# Import the NFO manager to test the IMDb parsing
|
||||||
|
try:
|
||||||
|
from core.nfo_manager import NFOManager
|
||||||
|
nfo_manager = NFOManager()
|
||||||
|
print("✅ Successfully imported NFOManager")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to import NFOManager: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Simulate the scanning logic
|
||||||
|
total_movies_found = 0
|
||||||
|
|
||||||
|
for media_path in movie_paths:
|
||||||
|
print(f"\n🎬 Scanning: {media_path}")
|
||||||
|
|
||||||
|
# Check if path exists (this will fail on your local machine but show the logic)
|
||||||
|
if not media_path.exists():
|
||||||
|
print(f" ⚠️ Path doesn't exist locally (expected on dev machine): {media_path}")
|
||||||
|
print(f" ℹ️ On production server, this path should contain directories like:")
|
||||||
|
print(f" Example Movie [imdb-tt1234567] (2023)/")
|
||||||
|
print(f" Another Movie [imdb-tt7654321] (2022)/")
|
||||||
|
continue
|
||||||
|
|
||||||
|
movie_count = 0
|
||||||
|
for item in media_path.iterdir():
|
||||||
|
if item.is_dir():
|
||||||
|
imdb_id = nfo_manager.parse_imdb_from_path(item)
|
||||||
|
if imdb_id:
|
||||||
|
movie_count += 1
|
||||||
|
print(f" ✅ Found movie: {item.name} -> {imdb_id}")
|
||||||
|
else:
|
||||||
|
print(f" ⏭️ Skipped (no IMDb ID): {item.name}")
|
||||||
|
|
||||||
|
print(f" 📊 Found {movie_count} movies with IMDb IDs in {media_path}")
|
||||||
|
total_movies_found += movie_count
|
||||||
|
|
||||||
|
print(f"\n🎯 Total movies that would be processed: {total_movies_found}")
|
||||||
|
|
||||||
|
# Test IMDb ID parsing with sample directory names
|
||||||
|
print(f"\n🧪 Testing IMDb ID Parsing Logic:")
|
||||||
|
test_names = [
|
||||||
|
"The Dark Knight [imdb-tt0468569] (2008)",
|
||||||
|
"Inception [imdb-tt1375666] (2010)",
|
||||||
|
"Invalid Movie Name (2020)",
|
||||||
|
"Movie Without IMDb (2021)",
|
||||||
|
"Another Movie [imdb-tt1234567] (2023)"
|
||||||
|
]
|
||||||
|
|
||||||
|
for test_name in test_names:
|
||||||
|
test_path = Path(f"/fake/path/{test_name}")
|
||||||
|
imdb_id = nfo_manager.parse_imdb_from_path(test_path)
|
||||||
|
if imdb_id:
|
||||||
|
print(f" ✅ '{test_name}' -> {imdb_id}")
|
||||||
|
else:
|
||||||
|
print(f" ❌ '{test_name}' -> No IMDb ID found")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def show_manual_scan_instructions():
|
||||||
|
"""Show instructions for testing on the production server"""
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("📋 NEXT STEPS - Test on Production Server")
|
||||||
|
print("="*60)
|
||||||
|
print()
|
||||||
|
print("1. 🔧 Update your environment variables:")
|
||||||
|
print(" export MOVIE_PATHS='/media/Movies/movies,/media/Movies/movies6'")
|
||||||
|
print(" export TV_PATHS='/media/TV/tv,/media/TV/tv6'")
|
||||||
|
print()
|
||||||
|
print("2. 🚀 Test the manual scan:")
|
||||||
|
print(" curl -X POST 'http://localhost:8080/manual/scan?scan_type=movies'")
|
||||||
|
print()
|
||||||
|
print("3. 📊 Check the logs for movie processing:")
|
||||||
|
print(" docker logs <container_name> | grep -E '(Processing movie|Completed movie scan)'")
|
||||||
|
print()
|
||||||
|
print("4. 🧪 Test the bulk update script:")
|
||||||
|
print(" python3 test_bulk_update.py")
|
||||||
|
print(" python3 bulk_update_movies.py")
|
||||||
|
print()
|
||||||
|
print("5. ✅ Verify end-to-end workflow:")
|
||||||
|
print(" curl http://localhost:8080/debug/movie/tt1674782")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_movie_scanning()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n✅ Local testing completed successfully!")
|
||||||
|
show_manual_scan_instructions()
|
||||||
|
else:
|
||||||
|
print("\n❌ Local testing failed!")
|
||||||
|
sys.exit(1)
|
||||||
Reference in New Issue
Block a user