From 935307a74d72e16930cccfe47645f9e0ece49c4d Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Mon, 8 Sep 2025 17:56:43 -0400 Subject: [PATCH] update to database only --- clients/radarr_client.py | 119 +++++++++++++----------------------- clients/radarr_db_client.py | 23 ++++--- 2 files changed, 56 insertions(+), 86 deletions(-) diff --git a/clients/radarr_client.py b/clients/radarr_client.py index efb5d0d..71becaf 100644 --- a/clients/radarr_client.py +++ b/clients/radarr_client.py @@ -61,16 +61,20 @@ class RadarrClient: self.timeout = timeout self.retries = max(0, retries) - # Initialize database client if available + # Initialize database client - REQUIRED for operation self.db_client = None if RadarrDbClient: try: self.db_client = RadarrDbClient.from_env() if self.db_client: - _log("INFO", "Enhanced performance mode: Direct database access enabled") + _log("INFO", "✅ DATABASE ONLY MODE: Direct database access enabled") + else: + _log("ERROR", "❌ DATABASE ONLY MODE: Database configuration required - API mode disabled") except Exception as e: - _log("WARNING", f"Failed to initialize database client, using API fallback: {e}") + _log("ERROR", f"❌ DATABASE ONLY MODE: Failed to initialize database client: {e}") self.db_client = None + else: + _log("ERROR", "❌ DATABASE ONLY MODE: RadarrDbClient not available - check dependencies") def _get(self, path: str, params: Dict[str, Any] = None) -> Optional[Any]: """Make GET request to Radarr API with retries""" @@ -107,53 +111,26 @@ class RadarrClient: return None def movie_by_imdb(self, imdb_id: str) -> Optional[Dict[str, Any]]: - """Find movie by IMDb ID with enhanced validation""" + """Find movie by IMDb ID - DATABASE ONLY mode""" imdb_id = imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}" _log("DEBUG", f"Looking up movie by IMDb ID: {imdb_id}") - # Try database first if available + # Database required - no API fallback if self.db_client: try: movie = self.db_client.get_movie_by_imdb(imdb_id) if movie: - _log("INFO", f"Found via database: {movie.get('title')} (ID: {movie.get('id')})") + _log("INFO", f"✅ Found via database: {movie.get('title')} (ID: {movie.get('id')})") return movie + else: + _log("WARNING", f"Movie not found in database for IMDb ID: {imdb_id}") + return None except Exception as e: - _log("WARNING", f"Database lookup failed, using API fallback: {e}") + _log("ERROR", f"Database lookup failed: {e}") + return None - # API Fallback methods - # Method 1: Direct lookup by ID - movie = self._get(f"/api/v3/movie/lookup/imdb/{imdb_id}") - if isinstance(movie, dict) and movie.get("imdbId", "").lower() == imdb_id.lower(): - _log("INFO", f"Found via API direct lookup: {movie.get('title')} (ID: {movie.get('id')})") - return movie - - # Method 2: Get all movies and filter by IMDb (fallback) - all_movies = self._get("/api/v3/movie") - if isinstance(all_movies, list): - _log("DEBUG", f"Got {len(all_movies)} total movies, filtering by IMDb ID") - for movie in all_movies: - movie_imdb = movie.get("imdbId", "").lower() - if movie_imdb == imdb_id.lower(): - _log("INFO", f"Found exact match: {movie.get('title')} (ID: {movie.get('id')})") - return movie - - # Method 3: Direct API search with validation - result = self._get("/api/v3/movie", {"imdbId": imdb_id}) - if isinstance(result, list) and result: - for movie in result: - movie_imdb = movie.get("imdbId", "").lower() - if movie_imdb == imdb_id.lower(): - _log("INFO", f"Found via search: {movie.get('title')} (ID: {movie.get('id')})") - return movie - - # Method 4: Lookup endpoint - lookup_result = self._get("/api/v3/movie/lookup/imdb", {"imdbId": imdb_id}) - if isinstance(lookup_result, dict) and lookup_result.get("imdbId", "").lower() == imdb_id.lower(): - _log("INFO", f"Found via lookup: {lookup_result.get('title')} (ID: {lookup_result.get('id')})") - return lookup_result - - _log("ERROR", f"No movie found for IMDb ID: {imdb_id}") + # No database client available + _log("ERROR", "Database client required for movie lookup - API mode disabled") return None def _analyze_event_for_import(self, event: Dict[str, Any], movie_info: Dict[str, Any] = None) -> Tuple[bool, str, Optional[str]]: @@ -441,62 +418,50 @@ class RadarrClient: return None def movie_files(self, movie_id: int) -> List[Dict[str, Any]]: - """Get movie files for a movie""" - result = self._get("/api/v3/moviefile", {"movieId": movie_id}) - return result if isinstance(result, list) else [] + """Get movie files for a movie - DATABASE ONLY mode""" + if self.db_client: + _log("INFO", "Using database for movie files lookup") + # Database handles this internally in get_movie_file_date() + return [] + + _log("ERROR", "Database client required for movie files - API mode disabled") + return [] def earliest_file_dateadded(self, movie_id: int) -> Optional[str]: - """Get earliest file dateAdded as fallback""" - files = self.movie_files(movie_id) - if not files: - return None - - earliest = None - for file_obj in files: - date_added = file_obj.get("dateAdded") - if not date_added: - continue - + """Get earliest file dateAdded - DATABASE ONLY mode""" + if self.db_client: try: - dt = datetime.fromisoformat(date_added.replace("Z", "+00:00")) - if earliest is None or dt < earliest: - earliest = dt - except Exception: - continue + return self.db_client.get_movie_file_date(movie_id) + except Exception as e: + _log("ERROR", f"Database file date query failed: {e}") + return None - if earliest: - return earliest.astimezone(timezone.utc).isoformat(timespec="seconds") + _log("ERROR", "Database client required for file dates - API mode disabled") return None def get_movie_import_date(self, movie_id: int, fallback_to_file_date: bool = True) -> Tuple[Optional[str], str]: """ - Get the best import date for a movie with database optimization. + Get the best import date for a movie - DATABASE ONLY mode. Returns: (date_iso, source_description) """ - # Try database first if available (much faster) + # Database required - no API fallback if self.db_client: try: date_iso, source = self.db_client.get_movie_import_date_optimized(movie_id, fallback_to_file_date) if date_iso: return date_iso, source + else: + _log("WARNING", f"No import date found in database for movie_id {movie_id}") + return None, "radarr:db.no_date_found" except Exception as e: - _log("WARNING", f"Database import date query failed, using API fallback: {e}") + _log("ERROR", f"Database import date query failed: {e}") + return None, "radarr:db.error" - # API Fallback: Try history parsing - import_date = self.earliest_import_event_optimized(movie_id) - if import_date: - return import_date, "radarr:api.history.import" - - # Fallback to file dateAdded if requested - if fallback_to_file_date: - file_date = self.earliest_file_dateadded(movie_id) - if file_date: - _log("WARNING", f"Using file dateAdded as fallback for movie_id {movie_id}") - return file_date, "radarr:api.file.dateAdded" - - return None, "radarr:no_date_found" + # No database client available + _log("ERROR", "Database client required for import date detection - API mode disabled") + return None, "radarr:db.not_configured" def _get_earliest_import_date(self, movie_id: int, movie_info: Dict) -> Optional[str]: """Get the earliest import date from Radarr history.""" diff --git a/clients/radarr_db_client.py b/clients/radarr_db_client.py index 49f8b60..2282283 100644 --- a/clients/radarr_db_client.py +++ b/clients/radarr_db_client.py @@ -175,26 +175,28 @@ class RadarrDbClient: Returns: (date_iso, source_description) """ - # Query for earliest import event (EventType = 3) + # Query for earliest import event - handle both numeric (3) and string ('downloadFolderImported') formats import_query = """ SELECT h."Date" as event_date, - h."Data" as event_data + h."Data" as event_data, + h."EventType" as event_type FROM "History" h WHERE h."MovieId" = %s - AND h."EventType" = 3 + AND (h."EventType" = 3 OR h."EventType" = 'downloadFolderImported' OR h."EventType" = 'movieFileImported') ORDER BY h."Date" ASC LIMIT 1 """ - # Fallback: earliest grab event (EventType = 1) with download info + # Fallback: earliest grab event - handle both numeric (1) and string ('grabbed') formats grab_query = """ SELECT h."Date" as event_date, - h."Data" as event_data + h."Data" as event_data, + h."EventType" as event_type FROM "History" h WHERE h."MovieId" = %s - AND h."EventType" = 1 + AND (h."EventType" = 1 OR h."EventType" = 'grabbed') AND h."Data" IS NOT NULL ORDER BY h."Date" ASC LIMIT 1 @@ -217,13 +219,14 @@ class RadarrDbClient: if row: event_date = row['event_date'] if self.db_type == "postgresql" else row[0] + event_type = row['event_type'] if self.db_type == "postgresql" else row[2] if isinstance(event_date, str): dt = datetime.fromisoformat(event_date.replace("Z", "+00:00")) else: dt = event_date.replace(tzinfo=timezone.utc) date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds") - _log("INFO", f"Found import event for movie {movie_id} at {date_iso}") + _log("INFO", f"✅ Found import event ({event_type}) for movie {movie_id} at {date_iso}") return date_iso, "radarr:db.history.import" # Fallback to grab events @@ -232,13 +235,14 @@ class RadarrDbClient: if row: event_date = row['event_date'] if self.db_type == "postgresql" else row[0] + event_type = row['event_type'] if self.db_type == "postgresql" else row[2] if isinstance(event_date, str): dt = datetime.fromisoformat(event_date.replace("Z", "+00:00")) else: dt = event_date.replace(tzinfo=timezone.utc) date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds") - _log("WARNING", f"Using grab event for movie {movie_id} at {date_iso}") + _log("WARNING", f"⚠️ Using grab event ({event_type}) for movie {movie_id} at {date_iso}") return date_iso, "radarr:db.history.grab" except Exception as e: @@ -342,7 +346,8 @@ class RadarrDbClient: MIN(h."Date") as earliest_import FROM "Movies" m JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id" - LEFT JOIN "History" h ON m."Id" = h."MovieId" AND h."EventType" = 3 + LEFT JOIN "History" h ON m."Id" = h."MovieId" + AND (h."EventType" = 3 OR h."EventType" = 'downloadFolderImported' OR h."EventType" = 'movieFileImported') WHERE mm."ImdbId" IN ({placeholders}) GROUP BY mm."ImdbId", m."Id" """