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)
|
||||
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
|
||||
# ---------------------------
|
||||
|
||||
@@ -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