diff --git a/media_manager/indexer/service.py b/media_manager/indexer/service.py index 9e25541..7a00569 100644 --- a/media_manager/indexer/service.py +++ b/media_manager/indexer/service.py @@ -1,6 +1,7 @@ from media_manager.indexer import log, indexers from media_manager.indexer.schemas import IndexerQueryResultId, IndexerQueryResult from media_manager.indexer.repository import IndexerRepository +from media_manager.notification.manager import notification_manager class IndexerService: @@ -20,9 +21,30 @@ class IndexerService: """ log.debug(f"Searching for: {query}") results = [] + failed_indexers = [] for indexer in indexers: - results.extend(indexer.search(query)) + try: + indexer_results = indexer.search(query) + results.extend(indexer_results) + log.debug(f"Indexer {indexer.__class__.__name__} returned {len(indexer_results)} results for query: {query}") + except Exception as e: + failed_indexers.append(indexer.__class__.__name__) + log.error(f"Indexer {indexer.__class__.__name__} failed for query '{query}': {e}") + + # Send notification if indexers failed + if failed_indexers and notification_manager.is_configured(): + notification_manager.send_notification( + title="Indexer Failure", + message=f"The following indexers failed for query '{query}': {', '.join(failed_indexers)}. Check indexer configuration and connectivity." + ) + + # Send notification if no results found from any indexer + if not results and notification_manager.is_configured(): + notification_manager.send_notification( + title="No Search Results", + message=f"No torrents found for query '{query}' from any configured indexer. Consider checking the search terms or indexer availability." + ) for result in results: self.repository.save_result(result=result) diff --git a/media_manager/metadataProvider/tmdb.py b/media_manager/metadataProvider/tmdb.py index ca88f79..e8c5a73 100644 --- a/media_manager/metadataProvider/tmdb.py +++ b/media_manager/metadataProvider/tmdb.py @@ -10,6 +10,7 @@ from media_manager.metadataProvider.abstractMetaDataProvider import ( from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult from media_manager.tv.schemas import Episode, Season, Show, SeasonNumber, EpisodeNumber from media_manager.movies.schemas import Movie +from media_manager.notification.manager import notification_manager class TmdbConfig(BaseSettings): @@ -29,29 +30,110 @@ class TmdbMetadataProvider(AbstractMetadataProvider): self.url = config.TMDB_RELAY_URL def __get_show_metadata(self, id: int) -> dict: - return requests.get(url=f"{self.url}/tv/shows/{id}").json() + try: + response = requests.get(url=f"{self.url}/tv/shows/{id}") + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error getting show metadata for ID {id}: {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to fetch show metadata for ID {id} from TMDB. Error: {str(e)}", + ) + raise def __get_season_metadata(self, show_id: int, season_number: int) -> dict: - return requests.get(url=f"{self.url}/tv/shows/{show_id}/{season_number}").json() + try: + response = requests.get( + url=f"{self.url}/tv/shows/{show_id}/{season_number}" + ) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error( + f"TMDB API error getting season {season_number} metadata for show ID {show_id}: {e}" + ) + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to fetch season {season_number} metadata for show ID {show_id} from TMDB. Error: {str(e)}", + ) + raise def __search_tv(self, query: str, page: int) -> dict: - return requests.get( - url=f"{self.url}/tv/search", params={"query": query, "page": page} - ).json() + try: + response = requests.get( + url=f"{self.url}/tv/search", params={"query": query, "page": page} + ) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error searching TV shows with query '{query}': {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to search TV shows with query '{query}' on TMDB. Error: {str(e)}", + ) + raise def __get_trending_tv(self) -> dict: - return requests.get(url=f"{self.url}/tv/trending").json() + try: + response = requests.get(url=f"{self.url}/tv/trending") + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error getting trending TV: {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to fetch trending TV shows from TMDB. Error: {str(e)}", + ) + raise def __get_movie_metadata(self, id: int) -> dict: - return requests.get(url=f"{self.url}/movies/{id}").json() + try: + response = requests.get(url=f"{self.url}/movies/{id}") + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error getting movie metadata for ID {id}: {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to fetch movie metadata for ID {id} from TMDB. Error: {str(e)}", + ) + raise def __search_movie(self, query: str, page: int) -> dict: - return requests.get( - url=f"{self.url}/movies/search", params={"query": query, "page": page} - ).json() + try: + response = requests.get( + url=f"{self.url}/movies/search", params={"query": query, "page": page} + ) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error searching movies with query '{query}': {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to search movies with query '{query}' on TMDB. Error: {str(e)}", + ) + raise def __get_trending_movies(self) -> dict: - return requests.get(url=f"{self.url}/movies/trending").json() + try: + response = requests.get(url=f"{self.url}/movies/trending") + response.raise_for_status() + return response.json() + except requests.RequestException as e: + log.error(f"TMDB API error getting trending movies: {e}") + if notification_manager.is_configured(): + notification_manager.send_notification( + title="TMDB API Error", + message=f"Failed to fetch trending movies from TMDB. Error: {str(e)}", + ) + raise def download_show_poster_image(self, show: Show) -> bool: show_metadata = self.__get_show_metadata(show.external_id) diff --git a/media_manager/tv/dependencies.py b/media_manager/tv/dependencies.py index cd1434b..527aff3 100644 --- a/media_manager/tv/dependencies.py +++ b/media_manager/tv/dependencies.py @@ -10,6 +10,7 @@ from media_manager.exceptions import NotFoundError from fastapi import HTTPException from media_manager.indexer.dependencies import indexer_service_dep from media_manager.torrent.dependencies import torrent_service_dep +from media_manager.notification.dependencies import notification_service_dep def get_tv_repository(db_session: DbSessionDependency) -> TvRepository: @@ -23,11 +24,13 @@ def get_tv_service( tv_repository: tv_repository_dep, torrent_service: torrent_service_dep, indexer_service: indexer_service_dep, + notification_service: notification_service_dep, ) -> TvService: return TvService( tv_repository=tv_repository, torrent_service=torrent_service, indexer_service=indexer_service, + notification_service=notification_service, ) diff --git a/media_manager/tv/service.py b/media_manager/tv/service.py index cd53862..e8ada0d 100644 --- a/media_manager/tv/service.py +++ b/media_manager/tv/service.py @@ -9,6 +9,7 @@ from media_manager.database import SessionLocal from media_manager.indexer.schemas import IndexerQueryResult from media_manager.indexer.schemas import IndexerQueryResultId from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult +from media_manager.notification.service import NotificationService from media_manager.torrent.schemas import Torrent, TorrentStatus, Quality from media_manager.torrent.service import TorrentService from media_manager.tv import log @@ -51,10 +52,12 @@ class TvService: tv_repository: TvRepository, torrent_service: TorrentService, indexer_service: IndexerService, + notification_service: NotificationService = None, ): self.tv_repository = tv_repository self.torrent_service = torrent_service self.indexer_service = indexer_service + self.notification_service = notification_service def add_show( self, external_id: int, metadata_provider: AbstractMetadataProvider @@ -567,7 +570,12 @@ class TvService: import_file(target_file=target_video_file, source_file=file) break else: - # TODO: notify admin that no video file was found for this episode + # Send notification about missing episode file + if self.notification_service: + self.notification_service.send_notification_to_all_providers( + title="Missing Episode File", + message=f"No video file found for S{season.number:02d}E{episode.number:02d} in torrent '{torrent.title}' for show {show.name}. Manual intervention may be required." + ) success = False log.warning( f"S{season.number}E{episode.number} in Torrent {torrent.title}'s files not found." @@ -575,6 +583,15 @@ class TvService: if success: torrent.imported = True self.torrent_service.torrent_repository.save_torrent(torrent=torrent) + + # Send successful season download notification + if self.notification_service: + season_info = ", ".join([f"Season {season_file.season_id}" for season_file in season_files]) + self.notification_service.send_notification_to_all_providers( + title="TV Season Downloaded", + message=f"Successfully downloaded {show.name} ({show.year}) - {season_info}" + ) + log.info(f"Finished organizing files for torrent {torrent.title}") def update_show_metadata(