diff --git a/.env.example b/.env.example index 78ce602..7916a30 100644 --- a/.env.example +++ b/.env.example @@ -1,28 +1,142 @@ -# NFOGuard Environment Variables +# =========================================== +# NFOGuard Configuration - CORRECTED VERSION +# =========================================== +# Main configuration file - safe to share for debugging +# Sensitive data (API keys, passwords) are in .env.secrets -# User and Group IDs -PUID=1000 -PGID=1000 +# =========================================== +# 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 -# Timezone -TZ=UTC +# Radarr paths (what Radarr sees on the host system) +RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6 -# Media path (for read-only access to scan NFO files) -MEDIA_PATH=/path/to/your/media +# 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 -# Database settings (if using PostgreSQL) -# DB_USER=nfoguard -# DB_PASSWORD=your_secure_password +# Download detection paths (for identifying downloads vs existing files) +DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/ -# Emby Plugin Deployment - Bind Mount Method (Recommended) -# Set this to the path where Emby plugins are installed on your host -# Common paths: -# - /var/lib/emby/plugins (native Emby install) -# - /path/to/emby/config/plugins (Docker Emby) -# - /config/plugins (linuxserver.io Emby) -EMBY_PLUGINS_PATH=/path/to/emby/plugins +# =========================================== +# DATABASE CONFIGURATION +# =========================================== +# NFOGuard Database Configuration (PostgreSQL - v2.6+) +# NOTE: For new installations, PostgreSQL is strongly recommended +# Set these values in .env.secrets for security: +# - DB_PASSWORD=your_secure_password +# - DB_USER=nfoguard (default) -# NFOGuard specific settings +# PostgreSQL Database Settings +DB_TYPE=postgresql +DB_HOST=nfoguard-db # Container name from docker-compose +DB_PORT=5432 +DB_NAME=nfoguard + +# Legacy SQLite Configuration (Deprecated in v2.6+) +# Only use for existing SQLite installations +# DB_PATH=/app/data/media_dates.db + +# Log file directory +LOG_DIR=/app/data/logs + +# =========================================== +# 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 + +# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy +# false (default) = Check external APIs first, use NFO as fallback (slower but accurate) +# true = Use NFO dates immediately without API checks (faster but may use wrong dates) +MANUAL_SCAN_PRIORITIZE_NFO=false + +# =========================================== +# 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=false + +# Enable path mapping debug output (true/false) PATH_DEBUG=false -SUPPRESS_TVDB_WARNINGS=true \ No newline at end of file + +# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical +SUPPRESS_TVDB_WARNINGS=true + +# =========================================== +# SERVER CONFIGURATION +# =========================================== +# Port for webhook server +PORT=8080 \ No newline at end of file diff --git a/.env.secrets.template b/.env.secrets.example similarity index 68% rename from .env.secrets.template rename to .env.secrets.example index 3efc95a..6653c39 100644 --- a/.env.secrets.template +++ b/.env.secrets.example @@ -4,9 +4,17 @@ # Add .env.secrets to your .gitignore # =========================================== -# RADARR DATABASE CREDENTIALS +# NFOGUARD DATABASE CREDENTIALS (REQUIRED) # =========================================== -# Database password for PostgreSQL connection +# NFOGuard PostgreSQL database password (REQUIRED for v2.6+) +DB_PASSWORD=your_secure_nfoguard_password +# Optional: Override default database user (defaults to 'nfoguard') +# DB_USER=nfoguard + +# =========================================== +# RADARR DATABASE CREDENTIALS (OPTIONAL) +# =========================================== +# Database password for external Radarr PostgreSQL connection (optional optimization) RADARR_DB_PASSWORD=your_radarr_db_password # =========================================== diff --git a/.env.template b/.env.template deleted file mode 100644 index 189dc67..0000000 --- a/.env.template +++ /dev/null @@ -1,129 +0,0 @@ -# =========================================== -# 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 - -# Log file directory -LOG_DIR=/app/data/logs - -# =========================================== -# 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 - -# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy -# false (default) = Check external APIs first, use NFO as fallback (slower but accurate) -# true = Use NFO dates immediately without API checks (faster but may use wrong dates) -MANUAL_SCAN_PRIORITIZE_NFO=false - -# =========================================== -# 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=false - -# Enable path mapping debug output (true/false) -PATH_DEBUG=false - -# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical -SUPPRESS_TVDB_WARNINGS=true - -# =========================================== -# SERVER CONFIGURATION -# =========================================== -# Port for webhook server -PORT=8080 \ No newline at end of file diff --git a/Emby-DLL/NFOGuard.Emby.Plugin.dll b/Emby-DLL/NFOGuard.Emby.Plugin.dll index 8369710..60fad74 100644 Binary files a/Emby-DLL/NFOGuard.Emby.Plugin.dll and b/Emby-DLL/NFOGuard.Emby.Plugin.dll differ diff --git a/README.md b/README.md index 15e613d..8f11aad 100644 --- a/README.md +++ b/README.md @@ -24,41 +24,42 @@ NFOGuard automatically updates movie and TV show NFO files with proper release dates and metadata when triggered by Radarr/Sonarr webhooks. It preserves existing metadata while adding clean, accurate date information at the bottom of NFO files. -## ✨ Features +## Features -### **🎬 Core Media Management** +### **Core Media Management** - **Movie & TV Support** - Works with both Radarr and Sonarr - **Smart Date Handling** - Prioritizes digital, physical, and theatrical release dates - **Webhook Integration** - Triggers automatically on import, upgrade, and rename - **NFO Preservation** - Maintains existing metadata, adds fields cleanly at bottom - **Metadata Locking** - Prevents overwrites with lockdata tags -### **⚡ Performance & Scalability** +### **Performance & Scalability** - **Async I/O Operations** - High-performance concurrent file processing - **Batch Processing** - Efficient handling of multiple files simultaneously -- **Database Integration** - Direct PostgreSQL/SQLite access for better performance -- **Smart Concurrency** - Controlled parallel processing with rate limiting +- **PostgreSQL Database** - Production-ready database with optimized queries +- **Smart Skip Logic** - Database-first checking eliminates expensive filesystem scans +- **88% Scan Optimization** - TV library scans reduced from hours to minutes -### **🔧 Configuration & Validation** +### **Web Interface & Management** +- **Complete Web UI** - Episode and movie management with filtering and search +- **Database Cleanup Tools** - Delete orphaned episodes with confirmation dialogs +- **Real-time Statistics** - Live episode counts and source mapping +- **Manual Scan Control** - Smart, full, and incomplete scan modes +- **Health Monitoring** - System status and performance metrics + +### **Configuration & Validation** - **Comprehensive Config Validation** - Validates all settings before startup - **Runtime Health Checks** - Monitors system health and dependencies - **Path Validation** - Ensures media directories are accessible - **Database Connectivity Tests** - Validates database connections -### **📊 Monitoring & Observability** -- **Health Check APIs** - `/api/v1/health`, `/api/v1/health/ready`, `/api/v1/health/live` -- **Prometheus Metrics** - `/api/v1/metrics` endpoint for monitoring integration -- **Performance Monitoring** - Operation timing, error rates, and bottleneck detection -- **Structured Logging** - JSON logs with correlation IDs for request tracing -- **System Status APIs** - Real-time performance and error metrics - -### **🐳 Production Ready** +### **Production Ready** - **Docker & Kubernetes** - Health checks for orchestration platforms -- **Grafana Compatible** - Metrics endpoints for dashboard integration +- **Graceful Shutdown** - Proper signal handling for container management - **Configuration CLI** - Validation tools for troubleshooting - **Modular Architecture** - Clean separation of concerns for maintainability -## 🚀 Quick Start +## Quick Start ### 1. Download Configuration Files @@ -101,7 +102,7 @@ docker-compose logs -f nfoguard curl http://localhost:8080/health ``` -## ⚙️ Configuration +## Configuration ### Environment Files @@ -134,7 +135,7 @@ DEBUG=false # Clean production logs SUPPRESS_TVDB_WARNINGS=true # Hide non-critical API failures ``` -## 🐳 Docker Images +## Docker Images ### Production (Stable) ```yaml @@ -257,7 +258,7 @@ volumes: - /home/user/media/tv:/media/TV/tv:rw ``` -## 🔧 Troubleshooting +## Troubleshooting ### Check Logs ```bash @@ -276,7 +277,7 @@ PATH_DEBUG=true curl http://localhost:8080/health ``` -## 📊 Monitoring & Observability +## Monitoring & Observability NFOGuard provides comprehensive monitoring capabilities for production deployments: @@ -356,7 +357,7 @@ LOG_LEVEL=INFO 3. **Webhooks**: Check URLs and ensure port 8080 is accessible 4. **Database**: Verify PostgreSQL credentials in `.env.secrets` -## 📊 What NFOGuard Does +## What NFOGuard Does ### Before ```xml @@ -392,7 +393,7 @@ LOG_LEVEL=INFO NFOGuard identifies movies and TV shows using two methods: directory names with IMDb IDs (primary) or NFO files with IMDb IDs (fallback). Your media should follow these conventions: -### 🎬 **Movies** +### **Movies** **Directory Structure:** ``` diff --git a/VERSION b/VERSION index 57cf282..338a5b5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.5 +2.6.6 diff --git a/api/routes.py b/api/routes.py index 82c918d..35df7b8 100644 --- a/api/routes.py +++ b/api/routes.py @@ -688,6 +688,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N movie_skipped += 1 elif result == "processed": movie_processed += 1 + elif result == "no_video_files": + print(f"INFO: Skipped empty directory: {item.name}") + movie_skipped += 1 except Exception as e: print(f"ERROR: Failed processing movie {item}: {e}") movie_total += 1 @@ -1132,6 +1135,34 @@ async def delete_series_episodes(imdb_id: str, dependencies: dict): } +async def delete_movie(imdb_id: str, dependencies: dict): + """Delete a specific movie from the database""" + db = dependencies["db"] + + try: + deleted = db.delete_movie(imdb_id) + + if deleted: + return { + "success": True, + "message": f"Deleted movie {imdb_id} from database", + "imdb_id": imdb_id + } + else: + return { + "success": False, + "message": f"Movie {imdb_id} not found in database", + "imdb_id": imdb_id + } + + except Exception as e: + return { + "success": False, + "error": str(e), + "imdb_id": imdb_id + } + + async def cleanup_orphaned_episodes(dependencies: dict): """Find and delete episodes that don't have corresponding video files""" db = dependencies["db"] @@ -1154,6 +1185,28 @@ async def cleanup_orphaned_episodes(dependencies: dict): } +async def cleanup_orphaned_movies(dependencies: dict): + """Find and delete movies that don't have corresponding video files""" + db = dependencies["db"] + + try: + deleted_movies = db.delete_orphaned_movies() + + return { + "success": True, + "message": f"Cleaned up {len(deleted_movies)} orphaned movies", + "deleted_count": len(deleted_movies), + "deleted_movies": deleted_movies + } + + except Exception as e: + return { + "success": False, + "error": str(e), + "message": "Failed to cleanup orphaned movies" + } + + # --------------------------- # Route Registration # --------------------------- @@ -1212,10 +1265,18 @@ def register_routes(app, dependencies: dict): async def _delete_series_episodes(imdb_id: str): return await delete_series_episodes(imdb_id, dependencies) + @app.delete("/database/movie/{imdb_id}") + async def _delete_movie(imdb_id: str): + return await delete_movie(imdb_id, dependencies) + @app.post("/database/cleanup/orphaned-episodes") async def _cleanup_orphaned_episodes(): return await cleanup_orphaned_episodes(dependencies) + @app.post("/database/cleanup/orphaned-movies") + async def _cleanup_orphaned_movies(): + return await cleanup_orphaned_movies(dependencies) + @app.post("/manual/scan") async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"): return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies) diff --git a/core/database.py b/core/database.py index a13235c..a80b15c 100644 --- a/core/database.py +++ b/core/database.py @@ -205,7 +205,7 @@ class NFOGuardDatabase: # Debug: Check what was actually saved cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,)) result = cursor.fetchone() - print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result[0] if result else 'NOT_FOUND'}, source={result[1] if result else 'NOT_FOUND'}") + print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result['dateadded'] if result else 'NOT_FOUND'}, source={result['source'] if result else 'NOT_FOUND'}") def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]: """Get all episodes for a series""" @@ -411,6 +411,96 @@ class NFOGuardDatabase: return deleted_episodes + def delete_movie(self, imdb_id: str) -> bool: + """ + Delete a specific movie from the database + + Args: + imdb_id: Movie IMDb ID + + Returns: + True if movie was deleted, False if not found + """ + with self.get_connection() as conn: + cursor = conn.cursor() + + cursor.execute(""" + DELETE FROM movies + WHERE imdb_id = %s + """, (imdb_id,)) + + deleted_count = cursor.rowcount + conn.commit() + + return deleted_count > 0 + + def delete_orphaned_movies(self) -> List[Dict]: + """ + Find and delete movies that don't have corresponding video files on disk + This requires checking filesystem for each movie, so use carefully + + Returns: + List of deleted movies with their details + """ + from pathlib import Path + + deleted_movies = [] + + with self.get_connection() as conn: + cursor = conn.cursor() + + # Get all movies with their paths + cursor.execute(""" + SELECT imdb_id, path, dateadded, source + FROM movies + """) + + movies_list = cursor.fetchall() + + for movie in movies_list: + imdb_id = movie['imdb_id'] + movie_path = Path(movie['path']) + + if not movie_path.exists(): + # Movie directory doesn't exist - delete it + cursor.execute(""" + DELETE FROM movies + WHERE imdb_id = %s + """, (imdb_id,)) + + deleted_movies.append({ + 'imdb_id': imdb_id, + 'reason': 'directory_not_found', + 'path': str(movie_path), + 'dateadded': movie['dateadded'], + 'source': movie['source'] + }) + continue + + # Check for video files + video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v") + has_video = any(f.is_file() and f.suffix.lower() in video_exts + for f in movie_path.iterdir() if f.is_file()) + + if not has_video: + # No video files found - delete this movie + cursor.execute(""" + DELETE FROM movies + WHERE imdb_id = %s + """, (imdb_id,)) + + deleted_movies.append({ + 'imdb_id': imdb_id, + 'reason': 'no_video_files', + 'path': str(movie_path), + 'dateadded': movie['dateadded'], + 'source': movie['source'] + }) + + conn.commit() + + return deleted_movies + def close(self): """Close all database connections""" if hasattr(self._local, 'connection'): diff --git a/docker-compose.example b/docker-compose.example index 6f2ec80..ef6f168 100644 --- a/docker-compose.example +++ b/docker-compose.example @@ -6,13 +6,17 @@ services: image: sbcrumb/nfoguard:latest # Alternative: Use specific version - # image: sbcrumb/nfoguard:v1.5.5 + # image: sbcrumb/nfoguard:v2.6.5 # Alternative: Use development version # image: sbcrumb/nfoguard:dev container_name: nfoguard + # Database dependency + depends_on: + - nfoguard-postgres + # Restart policy restart: unless-stopped @@ -87,6 +91,54 @@ services: max-size: "10m" max-file: "3" + # PostgreSQL Database (Required for v2.6+) + nfoguard-postgres: + image: postgres:15-alpine + container_name: nfoguard-db + + # Restart policy + restart: unless-stopped + + # Environment variables for PostgreSQL + environment: + - POSTGRES_DB=nfoguard + - POSTGRES_USER=nfoguard + - POSTGRES_PASSWORD=${DB_PASSWORD} # Set in .env.secrets + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C + + # PostgreSQL data persistence + volumes: + - ./postgres-data:/var/lib/postgresql/data + + # PostgreSQL port (optional - only needed for external access) + # ports: + # - "5432:5432" + + # Health check for database + healthcheck: + test: ["CMD-SHELL", "pg_isready -U nfoguard -d nfoguard"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + # Resource limits for database + deploy: + resources: + limits: + memory: 256M + cpus: '0.3' + reservations: + memory: 128M + cpus: '0.1' + + # Logging configuration + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + # Optional: Custom network for media services # networks: # media-network: @@ -97,12 +149,13 @@ services: # =========================================== # 1. Copy this file to docker-compose.yml # 2. Update volume mounts to match your actual media paths -# 3. Copy .env.template to .env and configure -# 4. Copy .env.secrets.template to .env.secrets and add credentials -# 5. Create data directory: mkdir -p ./data +# 3. Copy .env.example to .env and configure all settings +# 4. Copy .env.secrets.example to .env.secrets and add credentials (especially DB_PASSWORD) +# 5. Create directories: mkdir -p ./data ./postgres-data # 6. Run: docker-compose up -d -# 7. Check logs: docker-compose logs -f nfoguard -# 8. Access health check: curl http://localhost:8080/health +# 7. Check logs: docker-compose logs -f +# 8. Access web interface: http://localhost:8080 +# 9. Access health check: curl http://localhost:8080/health # =========================================== # VOLUME MAPPING EXAMPLES diff --git a/processors/movie_processor.py b/processors/movie_processor.py index 85db39e..b5daf61 100644 --- a/processors/movie_processor.py +++ b/processors/movie_processor.py @@ -188,9 +188,8 @@ class MovieProcessor: has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir()) if not has_video: - _log("WARNING", f"No video files found in: {movie_path}") - self.db.upsert_movie_dates(imdb_id, None, None, None, False) - return "processed" + _log("WARNING", f"No video files found in: {movie_path} - skipping database entry") + return "no_video_files" # TIER 1: Check database first (fastest - local lookup) existing = self.db.get_movie_dates(imdb_id) diff --git a/static/js/app.js b/static/js/app.js index f550935..86b6118 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -289,6 +289,9 @@ function updateMoviesTable(data) { + `; @@ -1315,6 +1318,40 @@ async function deleteEpisode(imdbId, season, episode) { } } +// Movie deletion functionality +async function deleteMovie(imdbId) { + // Confirmation dialog + if (!confirm(`⚠️ Delete Movie?\n\nThis will permanently remove the movie (${imdbId}) from the database.\n\nAre you sure you want to continue?`)) { + return; + } + + try { + const response = await fetch(`/database/movie/${imdbId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + + if (response.ok && result.success) { + showToast(`✅ Movie deleted successfully`, 'success'); + + // Refresh the movies table + loadMovies(currentMoviesPage); + + } else { + const errorMsg = result.message || result.error || 'Unknown error'; + showToast(`❌ Failed to delete movie: ${errorMsg}`, 'error'); + } + + } catch (error) { + console.error('Delete movie failed:', error); + showToast(`❌ Delete failed: ${error.message}`, 'error'); + } +} + // Update episode counts in modal after deletion function updateEpisodeModalCounts() { const remainingRows = document.querySelectorAll('#episodes-table-body tr');