update to database only
This commit is contained in:
+42
-77
@@ -61,16 +61,20 @@ class RadarrClient:
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.retries = max(0, retries)
|
self.retries = max(0, retries)
|
||||||
|
|
||||||
# Initialize database client if available
|
# Initialize database client - REQUIRED for operation
|
||||||
self.db_client = None
|
self.db_client = None
|
||||||
if RadarrDbClient:
|
if RadarrDbClient:
|
||||||
try:
|
try:
|
||||||
self.db_client = RadarrDbClient.from_env()
|
self.db_client = RadarrDbClient.from_env()
|
||||||
if self.db_client:
|
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:
|
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
|
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]:
|
def _get(self, path: str, params: Dict[str, Any] = None) -> Optional[Any]:
|
||||||
"""Make GET request to Radarr API with retries"""
|
"""Make GET request to Radarr API with retries"""
|
||||||
@@ -107,53 +111,26 @@ class RadarrClient:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def movie_by_imdb(self, imdb_id: str) -> Optional[Dict[str, Any]]:
|
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}"
|
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}")
|
_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:
|
if self.db_client:
|
||||||
try:
|
try:
|
||||||
movie = self.db_client.get_movie_by_imdb(imdb_id)
|
movie = self.db_client.get_movie_by_imdb(imdb_id)
|
||||||
if movie:
|
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
|
return movie
|
||||||
|
else:
|
||||||
|
_log("WARNING", f"Movie not found in database for IMDb ID: {imdb_id}")
|
||||||
|
return None
|
||||||
except Exception as e:
|
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
|
# No database client available
|
||||||
# Method 1: Direct lookup by ID
|
_log("ERROR", "Database client required for movie lookup - API mode disabled")
|
||||||
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}")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _analyze_event_for_import(self, event: Dict[str, Any], movie_info: Dict[str, Any] = None) -> Tuple[bool, str, Optional[str]]:
|
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
|
return None
|
||||||
|
|
||||||
def movie_files(self, movie_id: int) -> List[Dict[str, Any]]:
|
def movie_files(self, movie_id: int) -> List[Dict[str, Any]]:
|
||||||
"""Get movie files for a movie"""
|
"""Get movie files for a movie - DATABASE ONLY mode"""
|
||||||
result = self._get("/api/v3/moviefile", {"movieId": movie_id})
|
if self.db_client:
|
||||||
return result if isinstance(result, list) else []
|
_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]:
|
def earliest_file_dateadded(self, movie_id: int) -> Optional[str]:
|
||||||
"""Get earliest file dateAdded as fallback"""
|
"""Get earliest file dateAdded - DATABASE ONLY mode"""
|
||||||
files = self.movie_files(movie_id)
|
if self.db_client:
|
||||||
if not files:
|
|
||||||
return None
|
|
||||||
|
|
||||||
earliest = None
|
|
||||||
for file_obj in files:
|
|
||||||
date_added = file_obj.get("dateAdded")
|
|
||||||
if not date_added:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dt = datetime.fromisoformat(date_added.replace("Z", "+00:00"))
|
return self.db_client.get_movie_file_date(movie_id)
|
||||||
if earliest is None or dt < earliest:
|
except Exception as e:
|
||||||
earliest = dt
|
_log("ERROR", f"Database file date query failed: {e}")
|
||||||
except Exception:
|
return None
|
||||||
continue
|
|
||||||
|
|
||||||
if earliest:
|
_log("ERROR", "Database client required for file dates - API mode disabled")
|
||||||
return earliest.astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_movie_import_date(self, movie_id: int, fallback_to_file_date: bool = True) -> Tuple[Optional[str], str]:
|
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:
|
Returns:
|
||||||
(date_iso, source_description)
|
(date_iso, source_description)
|
||||||
"""
|
"""
|
||||||
# Try database first if available (much faster)
|
# Database required - no API fallback
|
||||||
if self.db_client:
|
if self.db_client:
|
||||||
try:
|
try:
|
||||||
date_iso, source = self.db_client.get_movie_import_date_optimized(movie_id, fallback_to_file_date)
|
date_iso, source = self.db_client.get_movie_import_date_optimized(movie_id, fallback_to_file_date)
|
||||||
if date_iso:
|
if date_iso:
|
||||||
return date_iso, source
|
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:
|
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
|
# No database client available
|
||||||
import_date = self.earliest_import_event_optimized(movie_id)
|
_log("ERROR", "Database client required for import date detection - API mode disabled")
|
||||||
if import_date:
|
return None, "radarr:db.not_configured"
|
||||||
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"
|
|
||||||
|
|
||||||
def _get_earliest_import_date(self, movie_id: int, movie_info: Dict) -> Optional[str]:
|
def _get_earliest_import_date(self, movie_id: int, movie_info: Dict) -> Optional[str]:
|
||||||
"""Get the earliest import date from Radarr history."""
|
"""Get the earliest import date from Radarr history."""
|
||||||
|
|||||||
@@ -175,26 +175,28 @@ class RadarrDbClient:
|
|||||||
Returns:
|
Returns:
|
||||||
(date_iso, source_description)
|
(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 = """
|
import_query = """
|
||||||
SELECT
|
SELECT
|
||||||
h."Date" as event_date,
|
h."Date" as event_date,
|
||||||
h."Data" as event_data
|
h."Data" as event_data,
|
||||||
|
h."EventType" as event_type
|
||||||
FROM "History" h
|
FROM "History" h
|
||||||
WHERE h."MovieId" = %s
|
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
|
ORDER BY h."Date" ASC
|
||||||
LIMIT 1
|
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 = """
|
grab_query = """
|
||||||
SELECT
|
SELECT
|
||||||
h."Date" as event_date,
|
h."Date" as event_date,
|
||||||
h."Data" as event_data
|
h."Data" as event_data,
|
||||||
|
h."EventType" as event_type
|
||||||
FROM "History" h
|
FROM "History" h
|
||||||
WHERE h."MovieId" = %s
|
WHERE h."MovieId" = %s
|
||||||
AND h."EventType" = 1
|
AND (h."EventType" = 1 OR h."EventType" = 'grabbed')
|
||||||
AND h."Data" IS NOT NULL
|
AND h."Data" IS NOT NULL
|
||||||
ORDER BY h."Date" ASC
|
ORDER BY h."Date" ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -217,13 +219,14 @@ class RadarrDbClient:
|
|||||||
|
|
||||||
if row:
|
if row:
|
||||||
event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
|
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):
|
if isinstance(event_date, str):
|
||||||
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
|
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
|
||||||
else:
|
else:
|
||||||
dt = event_date.replace(tzinfo=timezone.utc)
|
dt = event_date.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
|
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"
|
return date_iso, "radarr:db.history.import"
|
||||||
|
|
||||||
# Fallback to grab events
|
# Fallback to grab events
|
||||||
@@ -232,13 +235,14 @@ class RadarrDbClient:
|
|||||||
|
|
||||||
if row:
|
if row:
|
||||||
event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
|
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):
|
if isinstance(event_date, str):
|
||||||
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
|
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
|
||||||
else:
|
else:
|
||||||
dt = event_date.replace(tzinfo=timezone.utc)
|
dt = event_date.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
|
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"
|
return date_iso, "radarr:db.history.grab"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -342,7 +346,8 @@ class RadarrDbClient:
|
|||||||
MIN(h."Date") as earliest_import
|
MIN(h."Date") as earliest_import
|
||||||
FROM "Movies" m
|
FROM "Movies" m
|
||||||
JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
|
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})
|
WHERE mm."ImdbId" IN ({placeholders})
|
||||||
GROUP BY mm."ImdbId", m."Id"
|
GROUP BY mm."ImdbId", m."Id"
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user