From d98cbc613d901f604194be752492f9370a476b9c Mon Sep 17 00:00:00 2001 From: Gaisberg Date: Thu, 3 Oct 2024 15:41:56 +0300 Subject: [PATCH] chore: continue with mips, program starts now. --- src/controllers/default.py | 8 +- src/controllers/items.py | 18 +-- src/controllers/scrape.py | 2 +- src/program/content/listrr.py | 4 +- src/program/content/mdblist.py | 10 +- src/program/content/overseerr.py | 4 +- src/program/content/plex_watchlist.py | 4 +- src/program/content/trakt.py | 4 +- src/program/db/db_functions.py | 92 +++++++------- src/program/downloaders/torbox.py | 4 +- src/program/indexers/trakt.py | 18 +-- src/program/libraries/symlink.py | 6 +- src/program/media/item.py | 168 ++++++++++++++----------- src/program/media/stream.py | 28 ++--- src/program/media/subtitle.py | 8 +- src/program/program.py | 18 ++- src/program/scrapers/jackett.py | 4 +- src/program/scrapers/prowlarr.py | 4 +- src/program/scrapers/shared.py | 8 +- src/program/settings/models.py | 14 ++- src/program/state_transition.py | 4 +- src/program/symlink.py | 14 +-- src/tests/test_container.py | 2 +- src/tests/test_db_functions.py | 174 +++++++++++++------------- src/tests/test_symlink_library.py | 2 +- src/utils/event_manager.py | 30 ++--- src/utils/websockets/manager.py | 2 +- 27 files changed, 345 insertions(+), 309 deletions(-) diff --git a/src/controllers/default.py b/src/controllers/default.py index 94d96d8c..5375a403 100644 --- a/src/controllers/default.py +++ b/src/controllers/default.py @@ -103,8 +103,8 @@ async def get_stats(_: Request): payload = {} with db.Session() as session: - movies_symlinks = session.execute(select(func.count(Movie._id)).where(Movie.symlinked == True)).scalar_one() - episodes_symlinks = session.execute(select(func.count(Episode._id)).where(Episode.symlinked == True)).scalar_one() + movies_symlinks = session.execute(select(func.count(Movie.id)).where(Movie.symlinked == True)).scalar_one() + episodes_symlinks = session.execute(select(func.count(Episode.id)).where(Episode.symlinked == True)).scalar_one() total_symlinks = movies_symlinks + episodes_symlinks total_movies = session.execute(select(func.count(Movie._id))).scalar_one() @@ -118,7 +118,7 @@ async def get_stats(_: Request): select(MediaItem._id) .where(MediaItem.last_state != States.Completed) ).scalars().all() - + incomplete_retries = {} if _incomplete_items: media_items = session.query(MediaItem).filter(MediaItem._id.in_(_incomplete_items)).all() @@ -127,7 +127,7 @@ async def get_stats(_: Request): states = {} for state in States: - states[state] = session.execute(select(func.count(MediaItem._id)).where(MediaItem.last_state == state)).scalar_one() + states[state] = session.execute(select(func.count(MediaItem.id)).where(MediaItem.last_state == state)).scalar_one() payload["total_items"] = total_items payload["total_movies"] = total_movies diff --git a/src/controllers/items.py b/src/controllers/items.py index e213f562..232bed94 100644 --- a/src/controllers/items.py +++ b/src/controllers/items.py @@ -74,11 +74,11 @@ async def get_items( if search: search_lower = search.lower() if search_lower.startswith("tt"): - query = query.where(MediaItem.imdb_id == search_lower) + query = query.where(MediaItem.ids["imdb_id"] == search_lower) else: query = query.where( (func.lower(MediaItem.title).like(f"%{search_lower}%")) | - (func.lower(MediaItem.imdb_id).like(f"%{search_lower}%")) + (func.lower(MediaItem.ids["imdb_id"]).like(f"%{search_lower}%")) ) if state: @@ -168,7 +168,7 @@ async def add_items( with db.Session() as _: for id in valid_ids: item = MediaItem({"imdb_id": id, "requested_by": "riven", "requested_at": datetime.now()}) - request.app.program.em.add_item(item) + request.app.program.em.add_item(item, "ApiAdd") return {"success": True, "message": f"Added {len(valid_ids)} item(s) to the queue"} @@ -180,7 +180,7 @@ async def add_items( async def get_item(request: Request, id: int): with db.Session() as session: try: - item = session.execute(select(MediaItem).where(MediaItem._id == id)).unique().scalar_one() + item = session.execute(select(MediaItem).where(MediaItem.id == id)).unique().scalar_one() except NoResultFound: raise HTTPException(status_code=404, detail="Item not found") return {"success": True, "item": item.to_extended_dict()} @@ -195,7 +195,7 @@ async def get_items_by_imdb_ids(request: Request, imdb_ids: str): with db.Session() as session: items = [] for id in ids: - item = session.execute(select(MediaItem).where(MediaItem.imdb_id == id)).unique().scalar_one() + item = session.execute(select(MediaItem).where(MediaItem.ids["imdb_id"] == id)).unique().scalar_one() if item: items.append(item) return {"success": True, "items": [item.to_extended_dict() for item in items]} @@ -219,7 +219,7 @@ async def reset_items( clear_streams(media_item) reset_media_item(media_item) except Exception as e: - logger.error(f"Failed to reset item with id {media_item._id}: {str(e)}") + logger.error(f"Failed to reset item with id {media_item.id}: {str(e)}") continue except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @@ -257,7 +257,7 @@ async def remove_item(request: Request, ids: str): if not media_items: raise ValueError("Invalid item ID(s) provided. Some items may not exist.") for media_item in media_items: - logger.debug(f"Removing item {media_item.title} with ID {media_item._id}") + logger.debug(f"Removing item {media_item.title} with ID {media_item.id}") request.app.program.em.cancel_job(media_item) await asyncio.sleep(0.1) # Ensure cancellation is processed clear_streams(media_item) @@ -375,7 +375,7 @@ def set_torrent_rd(request: Request, id: int, torrent_id: str): # items = [] # return_dict = {} # for id in ids: -# items.append(session.execute(select(MediaItem).where(MediaItem._id == id)).unique().scalar_one()) +# items.append(session.execute(select(MediaItem).where(MediaItem.id == id)).unique().scalar_one()) # if any(item for item in items if item.type in ["Season", "Episode"]): # raise HTTPException(status_code=400, detail="Only shows and movies can be manually scraped currently") # for item in items: @@ -393,7 +393,7 @@ def set_torrent_rd(request: Request, id: int, torrent_id: str): # async def download(request: Request, id: str, hash: str): # downloader = request.app.program.services.get(Downloader).service # with db.Session() as session: -# item = session.execute(select(MediaItem).where(MediaItem._id == id)).unique().scalar_one() +# item = session.execute(select(MediaItem).where(MediaItem.id == id)).unique().scalar_one() # item.reset(True) # downloader.download_cached(item, hash) # request.app.program.add_to_queue(item) diff --git a/src/controllers/scrape.py b/src/controllers/scrape.py index deb833da..db459a88 100644 --- a/src/controllers/scrape.py +++ b/src/controllers/scrape.py @@ -33,7 +33,7 @@ async def scrape(request: Request, imdb_id: str, season: int = None, episode: in with db.Session() as session: media_item = session.execute( select(MediaItem).where( - MediaItem.imdb_id == imdb_id, + MediaItem.ids["imdb_id"] == imdb_id, MediaItem.type.in_(["movie", "show"]) ) ).unique().scalar_one_or_none() diff --git a/src/program/content/listrr.py b/src/program/content/listrr.py index 03203ccc..b17d6910 100644 --- a/src/program/content/listrr.py +++ b/src/program/content/listrr.py @@ -68,8 +68,8 @@ def run(self) -> Generator[MediaItem, None, None]: listrr_items = movie_items + show_items non_existing_items = _filter_existing_items(listrr_items) - new_non_recurring_items = [item for item in non_existing_items if item.imdb_id not in self.recurring_items] - self.recurring_items.update([item.imdb_id for item in new_non_recurring_items]) + new_non_recurring_items = [item for item in non_existing_items if item.ids["imdb_id"] not in self.recurring_items] + self.recurring_items.update([item.ids["imdb_id"] for item in new_non_recurring_items]) if new_non_recurring_items: logger.info(f"Fetched {len(new_non_recurring_items)} new items from Listrr") diff --git a/src/program/content/mdblist.py b/src/program/content/mdblist.py index af00aa36..fe6068bf 100644 --- a/src/program/content/mdblist.py +++ b/src/program/content/mdblist.py @@ -54,18 +54,18 @@ def run(self) -> Generator[MediaItem, None, None]: else: items = list_items_by_url(list, self.settings.api_key) for item in items: - if hasattr(item, "error") or not item or item.imdb_id is None: + if hasattr(item, "error") or not item or item.ids["imdb_id"] is None: continue - if item.imdb_id.startswith("tt"): + if item.ids["imdb_id"].startswith("tt"): items_to_yield.append(MediaItem( - {"imdb_id": item.imdb_id, "requested_by": self.key} + {"imdb_id": item.ids["imdb_id"], "requested_by": self.key} )) except RateLimitExceeded: pass non_existing_items = _filter_existing_items(items_to_yield) - new_non_recurring_items = [item for item in non_existing_items if item.imdb_id not in self.recurring_items and isinstance(item, MediaItem)] - self.recurring_items.update([item.imdb_id for item in new_non_recurring_items]) + new_non_recurring_items = [item for item in non_existing_items if item.ids["imdb_id"] not in self.recurring_items and isinstance(item, MediaItem)] + self.recurring_items.update([item.ids["imdb_id"] for item in new_non_recurring_items]) if new_non_recurring_items: logger.info(f"Found {len(new_non_recurring_items)} new items to fetch") diff --git a/src/program/content/overseerr.py b/src/program/content/overseerr.py index ff18a911..901716fb 100644 --- a/src/program/content/overseerr.py +++ b/src/program/content/overseerr.py @@ -59,8 +59,8 @@ def run(self): overseerr_items: list[MediaItem] = self.get_media_requests() non_existing_items = _filter_existing_items(overseerr_items) - new_non_recurring_items = [item for item in non_existing_items if item.imdb_id not in self.recurring_items and isinstance(item, MediaItem)] - self.recurring_items.update([item.imdb_id for item in new_non_recurring_items]) + new_non_recurring_items = [item for item in non_existing_items if item.ids["imdb_id"] not in self.recurring_items and isinstance(item, MediaItem)] + self.recurring_items.update([item.ids["imdb_id"] for item in new_non_recurring_items]) if self.settings.use_webhook: logger.debug("Webhook is enabled. Running Overseerr once before switching to webhook only mode") diff --git a/src/program/content/plex_watchlist.py b/src/program/content/plex_watchlist.py index 4853029f..57961cef 100644 --- a/src/program/content/plex_watchlist.py +++ b/src/program/content/plex_watchlist.py @@ -71,8 +71,8 @@ def run(self) -> Generator[MediaItem, None, None]: plex_items: set[str] = set(watchlist_items) | set(rss_items) items_to_yield: list[MediaItem] = [MediaItem({"imdb_id": imdb_id, "requested_by": self.key}) for imdb_id in plex_items if imdb_id and imdb_id.startswith("tt")] non_existing_items = _filter_existing_items(items_to_yield) - new_non_recurring_items = [item for item in non_existing_items if item.imdb_id not in self.recurring_items and isinstance(item, MediaItem)] - self.recurring_items.update([item.imdb_id for item in new_non_recurring_items]) + new_non_recurring_items = [item for item in non_existing_items if item.ids["imdb_id"] not in self.recurring_items and isinstance(item, MediaItem)] + self.recurring_items.update([item.ids["imdb_id"] for item in new_non_recurring_items]) if new_non_recurring_items: logger.info(f"Found {len(new_non_recurring_items)} new items to fetch") diff --git a/src/program/content/trakt.py b/src/program/content/trakt.py index f7314ed2..637132a4 100644 --- a/src/program/content/trakt.py +++ b/src/program/content/trakt.py @@ -97,10 +97,10 @@ def run(self): new_non_recurring_items = [ item for item in non_existing_items - if item.imdb_id not in self.recurring_items + if item.ids["imdb_id"] not in self.recurring_items and isinstance(item, MediaItem) ] - self.recurring_items.update(item.imdb_id for item in new_non_recurring_items) + self.recurring_items.update(item.ids["imdb_id"] for item in new_non_recurring_items) if new_non_recurring_items: logger.log("TRAKT", f"Found {len(new_non_recurring_items)} new items to fetch") diff --git a/src/program/db/db_functions.py b/src/program/db/db_functions.py index b49c7e50..1aa2dc09 100644 --- a/src/program/db/db_functions.py +++ b/src/program/db/db_functions.py @@ -25,7 +25,7 @@ def get_media_items_by_ids(media_item_ids: list[int]): with db.Session() as session: for media_item_id in media_item_ids: - item_type = session.execute(select(MediaItem.type).where(MediaItem._id==media_item_id)).scalar_one() + item_type = session.execute(select(MediaItem.type).where(MediaItem.id==media_item_id)).scalar_one() if not item_type: continue item = None @@ -33,24 +33,24 @@ def get_media_items_by_ids(media_item_ids: list[int]): case "movie": item = session.execute( select(Movie) - .where(MediaItem._id == media_item_id) + .where(MediaItem.id == media_item_id) ).unique().scalar_one() case "show": item = session.execute( select(Show) - .where(MediaItem._id == media_item_id) + .where(MediaItem.id == media_item_id) .options(joinedload(Show.seasons).joinedload(Season.episodes)) ).unique().scalar_one() case "season": item = session.execute( select(Season) - .where(Season._id == media_item_id) + .where(Season.id == media_item_id) .options(joinedload(Season.episodes)) ).unique().scalar_one() case "episode": item = session.execute( select(Episode) - .where(Episode._id == media_item_id) + .where(Episode.id == media_item_id) ).unique().scalar_one() if item: items.append(item) @@ -63,7 +63,7 @@ def get_parent_items_by_ids(media_item_ids: list[int]): with db.Session() as session: items = [] for media_item_id in media_item_ids: - item = session.execute(select(MediaItem).where(MediaItem._id == media_item_id, MediaItem.type.in_(["movie", "show"]))).unique().scalar_one_or_none() + item = session.execute(select(MediaItem).where(MediaItem.id == media_item_id, MediaItem.type.in_(["movie", "show"]))).unique().scalar_one_or_none() if item: items.append(item) return items @@ -72,7 +72,7 @@ def get_item_by_imdb_id(imdb_id: str): """Retrieve a MediaItem of type 'movie' or 'show' by an IMDb ID.""" from program.media.item import MediaItem with db.Session() as session: - item = session.execute(select(MediaItem).where(MediaItem.imdb_id == imdb_id, MediaItem.type.in_(["movie", "show"]))).unique().scalar_one_or_none() + item = session.execute(select(MediaItem).where(MediaItem.ids["imdb_id"] == imdb_id, MediaItem.type.in_(["movie", "show"]))).unique().scalar_one_or_none() return item def delete_media_item(item: "MediaItem"): @@ -134,11 +134,11 @@ def reset_streams(item: "MediaItem", active_stream_hash: str = None): blacklist_stream(item, stream, session) session.execute( - delete(StreamRelation).where(StreamRelation.parent_id == item._id) + delete(StreamRelation).where(StreamRelation.parent_id == item.id) ) session.execute( - delete(StreamBlacklistRelation).where(StreamBlacklistRelation.media_item_id == item._id) + delete(StreamBlacklistRelation).where(StreamBlacklistRelation.media_item_id == item.id) ) item.active_stream = {} session.commit() @@ -148,10 +148,10 @@ def clear_streams(item: "MediaItem"): with db.Session() as session: item = session.merge(item) session.execute( - delete(StreamRelation).where(StreamRelation.parent_id == item._id) + delete(StreamRelation).where(StreamRelation.parent_id == item.id) ) session.execute( - delete(StreamBlacklistRelation).where(StreamBlacklistRelation.media_item_id == item._id) + delete(StreamBlacklistRelation).where(StreamBlacklistRelation.media_item_id == item.id) ) session.commit() @@ -160,27 +160,27 @@ def blacklist_stream(item: "MediaItem", stream: Stream, session: Session = None) close_session = False if session is None: session = db.Session() - item = session.execute(select(type(item)).where(type(item)._id == item._id)).unique().scalar_one() + item = session.execute(select(type(item)).where(type(item).id == item.id)).unique().scalar_one() close_session = True try: item = session.merge(item) association_exists = session.query( session.query(StreamRelation) - .filter(StreamRelation.parent_id == item._id) - .filter(StreamRelation.child_id == stream._id) + .filter(StreamRelation.parent_id == item.id) + .filter(StreamRelation.child_id == stream.id) .exists() ).scalar() if association_exists: session.execute( delete(StreamRelation) - .where(StreamRelation.parent_id == item._id) - .where(StreamRelation.child_id == stream._id) + .where(StreamRelation.parent_id == item.id) + .where(StreamRelation.child_id == stream.id) ) session.execute( insert(StreamBlacklistRelation) - .values(media_item_id=item._id, stream_id=stream._id) + .values(media_item_id=item.id, stream_id=stream.id) ) item.store_state() session.commit() @@ -204,7 +204,7 @@ def filter_existing_streams(media_item_id: int, scraped_streams: List[Stream]) - existing_streams = session.execute( select(Stream.infohash) .join(Stream.parents) - .where(MediaItem._id == media_item_id) + .where(MediaItem.id == media_item_id) .where(Stream.infohash.in_(scraped_hashes)) ).scalars().all() existing_hashes = set(existing_streams) @@ -216,14 +216,14 @@ def get_stream_count(media_item_id: int) -> int: """Get the count of streams for a given MediaItem.""" with db.Session() as session: return session.execute( - select(func.count(Stream._id)) - .filter(Stream.parents.any(MediaItem._id == media_item_id)) + select(func.count(Stream.id)) + .filter(Stream.parents.any(MediaItem.id == media_item_id)) ).scalar_one() def load_streams_in_pages(session: Session, media_item_id: int, page_number: int, page_size: int = 5): """Load a specific page of streams for a given MediaItem.""" from program.media.item import MediaItem - stream_query = session.query(Stream._id, Stream.infohash).filter(Stream.parents.any(MediaItem._id == media_item_id)) + stream_query = session.query(Stream.id, Stream.infohash).filter(Stream.parents.any(MediaItem.id == media_item_id)) stream_chunk = stream_query.limit(page_size).offset(page_number * page_size).all() for stream_id, infohash in stream_chunk: @@ -233,13 +233,13 @@ def load_streams_in_pages(session: Session, media_item_id: int, page_number: int def _get_item_ids(session, item): from program.media.item import Episode, Season if item.type == "show": - show_id = item._id + show_id = item.id season_alias = aliased(Season, flat=True) - season_query = select(Season._id.label('id')).where(Season.parent_id == show_id) + season_query = select(Season.id.label('id')).where(Season.parent_id == show_id) episode_query = ( - select(Episode._id.label('id')) - .join(season_alias, Episode.parent_id == season_alias._id) + select(Episode.id.label('id')) + .join(season_alias, Episode.parent_id == season_alias.id) .where(season_alias.parent_id == show_id) ) @@ -248,28 +248,28 @@ def _get_item_ids(session, item): return show_id, related_ids elif item.type == "season": - season_id = item._id + season_id = item.id episode_ids = session.execute( - select(Episode._id) + select(Episode.id) .where(Episode.parent_id == season_id) ).scalars().all() return season_id, episode_ids elif item.type == "episode": - return item._id, [] + return item.id, [] elif hasattr(item, "parent"): - parent_id = item.parent._id + parent_id = item.parent.id return parent_id, [] - return item._id, [] + return item.id, [] def _ensure_item_exists_in_db(item: "MediaItem") -> bool: from program.media.item import MediaItem, Movie, Show if isinstance(item, (Movie, Show)): with db.Session() as session: - if item._id is None: - return session.execute(select(func.count(MediaItem._id)).where(MediaItem.imdb_id == item.imdb_id)).scalar_one() != 0 - return session.execute(select(func.count(MediaItem._id)).where(MediaItem._id == item._id)).scalar_one() != 0 - return bool(item and item._id) + if item.id is None: + return session.execute(select(func.count(MediaItem.id)).where(cast(MediaItem.ids["imdb_id"].astext, sqlalchemy.String) == item.ids["imdb_id"])).scalar_one() != 0 + return session.execute(select(func.count(MediaItem.id)).where(MediaItem.id == item.id)).scalar_one() != 0 + return bool(item and item.id) def _filter_existing_items(items: list["MediaItem"]) -> list["MediaItem"]: """Return a list of MediaItems that do not exist in the database.""" @@ -277,22 +277,22 @@ def _filter_existing_items(items: list["MediaItem"]) -> list["MediaItem"]: with db.Session() as session: existing_items = set( session.execute( - select(MediaItem.imdb_id) - .where(MediaItem.imdb_id.in_([item.imdb_id for item in items])) + select(MediaItem.ids["imdb_id"]) + .where(MediaItem.ids["imdb_id"].in_([item.ids["imdb_id"] for item in items])) ).scalars().all() ) - return [item for item in items if item.imdb_id not in existing_items] + return [item for item in items if item.ids["imdb_id"] not in existing_items] def _get_item_type_from_db(item: "MediaItem") -> str: from program.media.item import MediaItem with db.Session() as session: - if item._id is None: - return session.execute(select(MediaItem.type).where((MediaItem.imdb_id==item.imdb_id) & (MediaItem.type.in_(["show", "movie"])))).scalar_one() - return session.execute(select(MediaItem.type).where(MediaItem._id==item._id)).scalar_one() + if item.id is None: + return session.execute(select(MediaItem.type).where((cast(MediaItem.ids["imdb_id"].astext, sqlalchemy.String)==item.ids["imdb_id"]) & (MediaItem.type.in_(["show", "movie"])))).scalar_one() + return session.execute(select(MediaItem.type).where(MediaItem.id==item.id)).scalar_one() def _store_item(item: "MediaItem"): from program.media.item import Episode, Movie, Season, Show - if isinstance(item, (Movie, Show, Season, Episode)) and item._id is not None: + if isinstance(item, (Movie, Show, Season, Episode)) and item.id is not None: with db.Session() as session: item.store_state() session.merge(item) @@ -305,7 +305,7 @@ def _store_item(item: "MediaItem"): def _imdb_exists_in_db(imdb_id: str) -> bool: from program.media.item import MediaItem with db.Session() as session: - return session.execute(select(func.count(MediaItem._id)).where(MediaItem.imdb_id == imdb_id)).scalar_one() != 0 + return session.execute(select(func.count(MediaItem.id)).where(cast(MediaItem.ids['imdb_id'].astext, sqlalchemy.String) == imdb_id)).scalar_one() != 0 def _get_item_from_db(session, item: "MediaItem"): from program.media.item import Episode, MediaItem, Movie, Season, Show @@ -317,27 +317,27 @@ def _get_item_from_db(session, item: "MediaItem"): case "movie": r = session.execute( select(Movie) - .where(MediaItem.imdb_id == item.imdb_id) + .where(MediaItem.ids["imdb_id"] == item.ids["imdb_id"]) ).unique().scalar_one() return r case "show": r = session.execute( select(Show) - .where(MediaItem.imdb_id == item.imdb_id) + .where(MediaItem.ids["imdb_id"] == item.ids["imdb_id"]) .options(joinedload(Show.seasons).joinedload(Season.episodes)) ).unique().scalar_one() return r case "season": r = session.execute( select(Season) - .where(Season._id == item._id) + .where(Season.id == item.id) .options(joinedload(Season.episodes)) ).unique().scalar_one() return r case "episode": r = session.execute( select(Episode) - .where(Episode._id == item._id) + .where(Episode.id == item.id) ).unique().scalar_one() return r case _: diff --git a/src/program/downloaders/torbox.py b/src/program/downloaders/torbox.py index 590b540c..9777d0fc 100644 --- a/src/program/downloaders/torbox.py +++ b/src/program/downloaders/torbox.py @@ -73,7 +73,7 @@ def validate(self) -> bool: def run(self, item: MediaItem) -> bool: """Download media item from torbox.app""" return_value = False - stream_count = get_stream_count(item._id) + stream_count = get_stream_count(item.id) processed_stream_hashes = set() # Track processed stream hashes stream_hashes = {} @@ -82,7 +82,7 @@ def run(self, item: MediaItem) -> bool: for page_number in range(total_pages): with db.Session() as session: - for stream_id, infohash, stream in load_streams_in_pages(session, item._id, page_number, page_size=number_of_rows_per_page): + for stream_id, infohash, stream in load_streams_in_pages(session, item.id, page_number, page_size=number_of_rows_per_page): stream_hash_lower = infohash.lower() if stream_hash_lower in processed_stream_hashes: diff --git a/src/program/indexers/trakt.py b/src/program/indexers/trakt.py index 5cb9b223..9aa445f2 100644 --- a/src/program/indexers/trakt.py +++ b/src/program/indexers/trakt.py @@ -29,7 +29,7 @@ def copy_attributes(source, target): """Copy attributes from source to target.""" attributes = ["file", "folder", "update_folder", "symlinked", "is_anime", "symlink_path", "subtitles", "requested_by", "requested_at", "overseerr_id", "active_stream", "requested_id"] for attr in attributes: - target.set(attr, getattr(source, attr, None)) + setattr(target, attr, getattr(source, attr, None)) def copy_items(self, itema: MediaItem, itemb: MediaItem): """Copy attributes from itema to itemb recursively.""" @@ -48,7 +48,7 @@ def copy_items(self, itema: MediaItem, itemb: MediaItem): itemb.set("is_anime", is_anime) elif itemb.type == "movie": self.copy_attributes(itema, itemb) - itemb.set("is_anime", is_anime) + itemb.is_anime = is_anime else: logger.error(f"Item types {itema.type} and {itemb.type} do not match cant copy metadata") return itemb @@ -58,11 +58,11 @@ def run(self, in_item: MediaItem, log_msg: bool = True) -> Generator[Union[Movie if not in_item: logger.error("Item is None") return - if not (imdb_id := in_item.imdb_id): + if not (imdb_id := in_item.ids["imdb_id"]): logger.error(f"Item {in_item.log_string} does not have an imdb_id, cannot index it") return - if in_item.imdb_id in self.failed_ids: + if in_item.ids["imdb_id"] in self.failed_ids: return item_type = in_item.type if in_item.type != "mediaitem" else None @@ -74,19 +74,19 @@ def run(self, in_item: MediaItem, log_msg: bool = True) -> Generator[Union[Movie elif item.type == "movie": pass else: - logger.error(f"Indexed IMDb Id {item.imdb_id} returned the wrong item type: {item.type}") - self.failed_ids.add(in_item.imdb_id) + logger.error(f"Indexed IMDb Id {item.ids['imdb_id']} returned the wrong item type: {item.type}") + self.failed_ids.add(in_item.ids["imdb_id"]) return else: - logger.error(f"Failed to index item with imdb_id: {in_item.imdb_id}") - self.failed_ids.add(in_item.imdb_id) + logger.error(f"Failed to index item with imdb_id: {in_item.ids['imdb_id']}") + self.failed_ids.add(in_item.ids["imdb_id"]) return item = self.copy_items(in_item, item) item.indexed_at = datetime.now() if log_msg: # used for mapping symlinks to database, need to hide this log message - logger.debug(f"Indexed IMDb id ({in_item.imdb_id}) as {item.type.title()}: {item.log_string}") + logger.debug(f"Indexed IMDb id ({in_item.ids['imdb_id']}) as {item.type.title()}: {item.log_string}") yield item @staticmethod diff --git a/src/program/libraries/symlink.py b/src/program/libraries/symlink.py index 4f0ef36e..b1077f8a 100644 --- a/src/program/libraries/symlink.py +++ b/src/program/libraries/symlink.py @@ -304,9 +304,9 @@ def get_items_from_filepath(session: Session, filepath: str) -> list["Movie"] | for ep_num in episode_numbers: query = ( session.query(Episode) - .join(Season, Episode.parent_id == Season._id) - .join(Show, Season.parent_id == Show._id) - .filter(Show.imdb_id == imdb_id, Season.number == season_number, Episode.number == ep_num) + .join(Season, Episode.parent_id == Season.id) + .join(Show, Season.parent_id == Show.id) + .filter(Show.ids["imdb_id"] == imdb_id, Season.number == season_number, Episode.number == ep_num) ) episode_item = query.with_entities(Episode).first() if episode_item: diff --git a/src/program/media/item.py b/src/program/media/item.py index afc85728..bf804810 100644 --- a/src/program/media/item.py +++ b/src/program/media/item.py @@ -1,66 +1,83 @@ -"""MediaItem class""" from collections import defaultdict import json from datetime import datetime from pathlib import Path from typing import List, Optional, Self -import sqlalchemy from RTN import SettingsModel, parse -from sqlalchemy import Index -from sqlalchemy.orm import Mapped, mapped_column, object_session, relationship +import sqlalchemy +from sqlalchemy import Index, Column, Integer, String, DateTime, Boolean, ForeignKey, JSON, func +from sqlalchemy.orm import declarative_base, relationship, Mapped, mapped_column +from sqlalchemy.dialects.postgresql import JSONB +from program.settings.models import RivenSettingsModel import utils.websockets.manager as ws_manager from program.db.db import db from program.media.state import States from program.media.subtitle import Subtitle from utils.logger import logger -from sqlmodel import SQLModel, Field, Relationship, ForeignKey - -from ..db.db_functions import blacklist_stream, reset_streams +from ..db.db_functions import blacklist_stream from .stream import Stream + EPOCH = datetime.fromtimestamp(0) -class ProfileDataLink(SQLModel, table=True): - __tablename__ = "profile_data_link" - data_id: Optional[int] = Field(default=None, foreign_key="profile_data.id", primary_key=True) - profile_id: Optional[int] = Field(default=None, foreign_key="profiles.id", primary_key=True) +class ProfileDataLink(db.Model): + __tablename__ = 'profiledatalink' + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + data_id = Column(Integer, ForeignKey('profiledata.id')) + profile_id = Column(Integer, ForeignKey('profile.id')) -class Profile(SQLModel, SettingsModel, table=True): - __tablename__ = "profiles" +class Profile(db.Model): + __tablename__ = 'profile' + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + name: Mapped[str] = mapped_column(sqlalchemy.String, nullable=False) + profile_data = relationship("ProfileData", secondary="profiledatalink", back_populates="profile") + model = mapped_column(JSONB, nullable=False) - id: int = Field(primary_key=True) - profile_datas: list["ProfileData"] = Relationship(back_populates="profile", link_model=ProfileDataLink) + def __init__(self, model: RivenSettingsModel) -> None: + self.model = model.to_dict() + self.name = model.profile -class ProfileData(SQLModel, table=True): - __tablename__ = "profile_data" + @property + def settings_model(self) -> RivenSettingsModel: + # Convert the stored dictionary back to RTNSettingsModel when accessing + return RivenSettingsModel(**self.model) - id: int = Field(primary_key=True) - parent: "MediaItem" = Relationship("MediaItem", back_populates="profiles", cascade_delete=True) + @settings_model.setter + def settings_model(self, value: RivenSettingsModel): + # Convert RTNSettingsModel to dictionary when setting + self.model = value.to_dict() - profile: Profile = Relationship(back_populates="profile_datas", link_model=ProfileDataLink) - last_state: States - last_try: datetime +class ProfileData(db.Model): + __tablename__ = 'profiledata' + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + parent_id = Column(Integer, ForeignKey('mediaitem.id', ondelete="CASCADE")) + parent: Mapped["MediaItem"] = relationship("MediaItem", back_populates="profiles") - scraped_at: datetime - scraped_times: int - streams: list[Stream] = Relationship(secondary="StreamRelation", back_populates="parents", lazy="select", cascade="all") - blacklisted_streams: list[Stream] = Relationship(secondary="StreamBlacklistRelation", back_populates="blacklisted_parents", lazy="select", cascade="all") + profile: Mapped[Profile] = relationship("Profile", secondary="profiledatalink", back_populates="profile_data") + last_state = Column(sqlalchemy.Enum(States)) + last_try = Column(DateTime) - active_stream: Optional[Stream] = Relationship(ForeignKey("Stream._id"), nullable=True) - download_path: Optional[Path] + scraped_at = Column(DateTime) + scraped_times = Column(Integer) + streams: Mapped[List["Stream"]] = relationship(secondary="streamrelation", back_populates="parents") + blacklisted_streams: Mapped[List["Stream"]] = relationship(secondary="streamblacklistrelation", back_populates="blacklisted_parents") - symlink_path: Optional[Path] - symlinked_times: Optional[int] + active_stream_id = Column(Integer, ForeignKey('stream.id')) + active_stream: Mapped[Optional["Stream"]] = relationship("Stream") + download_path = Column(String) - subtitles: List[Subtitle] = Relationship("Subtitle", back_populates="parent", lazy="joined", cascade="all, delete-orphan") + symlink_path = Column(String) + symlinked_times = Column(Integer) - def __init__(self, profile: SettingsModel) -> None: + subtitles: Mapped[List["Subtitle"]] = relationship(back_populates="parent") + + def __init__(self, profile: Profile) -> None: self.last_state: States = States.Unknown - self.profile: Profile = Profile(**profile) + self.profile: Profile = profile self.scraped_at: datetime = EPOCH self.scraped_times: int = 0 @@ -122,33 +139,33 @@ def reset(self, soft_reset: bool = False): self.subtitles = [] class MediaItem(db.Model): - """MediaItem class""" - __tablename__ = "MediaItem" + __tablename__ = 'mediaitem' - id: Mapped[int] = mapped_column(primary_key=True) - type: Mapped[str] = mapped_column(sqlalchemy.String, nullable=False) + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + type = Column(String) + last_state = Column(sqlalchemy.Enum(States)) - title: Mapped[Optional[str]] = mapped_column(sqlalchemy.String, nullable=True) - year: Mapped[Optional[int]] = mapped_column(sqlalchemy.Integer, nullable=True) - genres: Mapped[Optional[List[str]]] = mapped_column(sqlalchemy.JSON, nullable=True) - language: Mapped[Optional[str]] = mapped_column(sqlalchemy.String, nullable=True) + title = Column(String) + year = Column(Integer) + genres = Column(JSONB) + language = Column(String) - ids: Mapped[Optional[dict]] = mapped_column(sqlalchemy.JSON, default=defaultdict({"imdb": None, "tmdb": None, "tvdb": None})) - network: Mapped[Optional[str]] = mapped_column(sqlalchemy.String, nullable=True) - country: Mapped[Optional[str]] = mapped_column(sqlalchemy.String, nullable=True) - aired_at: Mapped[Optional[datetime]] = mapped_column(sqlalchemy.DateTime, nullable=True) + ids = Column(JSONB, default={"imdb": "", "tvdb": "", "tmdb": ""}) + network = Column(String) + country = Column(String) + aired_at = Column(DateTime) - requested_at: Mapped[Optional[datetime]] = mapped_column(sqlalchemy.DateTime, default=datetime.now()) - requested_by: Mapped[Optional[str]] = mapped_column(sqlalchemy.String, nullable=True) - requested_id: Mapped[Optional[int]] = mapped_column(sqlalchemy.Integer, nullable=True) - indexed_at: Mapped[Optional[datetime]] = mapped_column(sqlalchemy.DateTime, nullable=True) + requested_at = Column(DateTime) + requested_by = Column(String) + requested_id = Column(Integer) + indexed_at = Column(DateTime) - aliases: Mapped[Optional[dict]] = mapped_column(sqlalchemy.JSON, default={}) - is_anime: Mapped[Optional[bool]] = mapped_column(sqlalchemy.Boolean, default=False) + aliases = Column(JSONB) + is_anime = Column(Boolean) - overseerr_id: Mapped[Optional[int]] = mapped_column(sqlalchemy.Integer, nullable=True) + overseerr_id = Column(Integer) - profiles: Mapped[list[ProfileData]] = relationship(ProfileData, back_populates="parent", lazy="joined", cascade="all") + profiles: Mapped[List["ProfileData"]] = relationship("ProfileData", back_populates="parent") __mapper_args__ = { "polymorphic_identity": "mediaitem", @@ -161,9 +178,9 @@ class MediaItem(db.Model): Index('ix_mediaitem_type', 'type'), Index('ix_mediaitem_requested_by', 'requested_by'), Index('ix_mediaitem_title', 'title'), - Index('ix_mediaitem_ids_imdb_id', 'ids["imdb"]'), - Index('ix_mediaitem_ids_tvdb_id', 'tvdb_id'), - Index('ix_mediaitem_ids_tmdb_id', 'tmdb_id'), + Index('ix_mediaitem_ids_imdb_id', func.cast(func.jsonb_extract_path_text(ids, 'imdb'), sqlalchemy.String)), + Index('ix_mediaitem_ids_tvdb_id', func.cast(func.jsonb_extract_path_text(ids, 'tvdb'), sqlalchemy.String)), + Index('ix_mediaitem_ids_tmdb_id', func.cast(func.jsonb_extract_path_text(ids, 'tmdb'), sqlalchemy.String)), Index('ix_mediaitem_network', 'network'), Index('ix_mediaitem_country', 'country'), Index('ix_mediaitem_language', 'language'), @@ -197,7 +214,12 @@ def __init__(self, item: dict | None) -> None: self.overseerr_id = item.get("overseerr_id") - self.profiles: list[MediaItemProfile] = [] + self.profiles: list[ProfileData] = [] + with db.Session() as session: + db_profiles = session.query(Profile).all() + for profile in db_profiles: + data = ProfileData(profile) + self.profiles.append(data) def store_state(self) -> None: _state = self._determine_state() @@ -308,12 +330,11 @@ def collection(self): return self.parent.collection if self.parent else self.ids["imdb_id"] class Movie(MediaItem): - """Movie class""" - __tablename__ = "Movie" - id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("MediaItem.id"), primary_key=True) + __tablename__ = 'movie' + id = Column(Integer, ForeignKey('mediaitem.id'), primary_key=True) + __mapper_args__ = { "polymorphic_identity": "movie", - "polymorphic_load": "inline", } def copy(self, other): @@ -328,14 +349,12 @@ def __repr__(self): return f"Movie:{self.log_string}:{self.last_state.name}" class Show(MediaItem): - """Show class""" - __tablename__ = "Show" - id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("MediaItem.id"), primary_key=True) + __tablename__ = 'show' + id = Column(Integer, ForeignKey('mediaitem.id'), primary_key=True) seasons: Mapped[List["Season"]] = relationship(back_populates="parent", foreign_keys="Season.parent_id", lazy="joined", cascade="all, delete-orphan", order_by="Season.number") __mapper_args__ = { "polymorphic_identity": "show", - "polymorphic_load": "inline", } def __init__(self, item): @@ -416,16 +435,15 @@ def propagate(target, source): class Season(MediaItem): - """Season class""" - __tablename__ = "Season" - id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("MediaItem.id"), primary_key=True) + __tablename__ = "season" + id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("mediaitem.id"), primary_key=True) number: Mapped[Optional[int]] = mapped_column(sqlalchemy.Integer, nullable=True) - parent_id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("Show.id"), use_existing_column=True) + parent_id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("show.id"), use_existing_column=True) parent: Mapped["Show"] = relationship(lazy=False, back_populates="seasons", foreign_keys="Season.parent_id") episodes: Mapped[List["Episode"]] = relationship(back_populates="parent", foreign_keys="Episode.parent_id", lazy="joined", cascade="all, delete-orphan", order_by="Episode.number") + __mapper_args__ = { "polymorphic_identity": "season", - "polymorphic_load": "inline", } def store_state(self) -> None: @@ -498,16 +516,14 @@ def get_top_title(self) -> str: class Episode(MediaItem): - """Episode class""" - __tablename__ = "Episode" - id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("MediaItem.id"), primary_key=True) - number: Mapped[Optional[int]] = mapped_column(sqlalchemy.Integer, nullable=True) - parent_id: Mapped[int] = mapped_column(sqlalchemy.ForeignKey("Season.id"), use_existing_column=True) + __tablename__ = 'episode' + id = Column(Integer, ForeignKey('mediaitem.id'), primary_key=True) + number = Column(Integer) + parent_id = Column(Integer, ForeignKey('season.id')) parent: Mapped["Season"] = relationship(back_populates="episodes", foreign_keys="Episode.parent_id", lazy="joined") __mapper_args__ = { "polymorphic_identity": "episode", - "polymorphic_load": "inline", } def __init__(self, item): diff --git a/src/program/media/stream.py b/src/program/media/stream.py index 8a4d5aad..58ba914b 100644 --- a/src/program/media/stream.py +++ b/src/program/media/stream.py @@ -14,23 +14,23 @@ class StreamRelation(db.Model): - __tablename__ = "StreamRelation" + __tablename__ = "streamrelation" - _id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) - parent_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("MediaItem._id", ondelete="CASCADE")) - child_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("Stream._id", ondelete="CASCADE")) + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + parent_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("profiledata.id", ondelete="CASCADE")) + child_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("stream.id", ondelete="CASCADE")) __table_args__ = ( Index('ix_streamrelation_parent_id', 'parent_id'), Index('ix_streamrelation_child_id', 'child_id'), ) - + class StreamBlacklistRelation(db.Model): - __tablename__ = "StreamBlacklistRelation" + __tablename__ = "streamblacklistrelation" - _id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) - media_item_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("MediaItem._id", ondelete="CASCADE")) - stream_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("Stream._id", ondelete="CASCADE")) + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + media_item_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("profiledata.id", ondelete="CASCADE")) + stream_id: Mapped[int] = mapped_column(sqlalchemy.Integer, sqlalchemy.ForeignKey("stream.id", ondelete="CASCADE")) __table_args__ = ( Index('ix_streamblacklistrelation_media_item_id', 'media_item_id'), @@ -38,17 +38,17 @@ class StreamBlacklistRelation(db.Model): ) class Stream(db.Model): - __tablename__ = "Stream" + __tablename__ = "stream" - _id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) + id: Mapped[int] = mapped_column(sqlalchemy.Integer, primary_key=True) infohash: Mapped[str] = mapped_column(sqlalchemy.String, nullable=False) raw_title: Mapped[str] = mapped_column(sqlalchemy.String, nullable=False) parsed_title: Mapped[str] = mapped_column(sqlalchemy.String, nullable=False) rank: Mapped[int] = mapped_column(sqlalchemy.Integer, nullable=False) lev_ratio: Mapped[float] = mapped_column(sqlalchemy.Float, nullable=False) - parents: Mapped[list["MediaItem"]] = relationship(secondary="StreamRelation", back_populates="streams") - blacklisted_parents: Mapped[list["MediaItem"]] = relationship(secondary="StreamBlacklistRelation", back_populates="blacklisted_streams") + parents: Mapped[list["ProfileData"]] = relationship(secondary="streamrelation", back_populates="streams") + blacklisted_parents: Mapped[list["ProfileData"]] = relationship(secondary="streamblacklistrelation", back_populates="blacklisted_streams") __table_args__ = ( Index('ix_stream_infohash', 'infohash'), @@ -66,6 +66,6 @@ def __init__(self, torrent: Torrent): def __hash__(self): return self.infohash - + def __eq__(self, other): return isinstance(other, Stream) and self.infohash == other.infohash \ No newline at end of file diff --git a/src/program/media/subtitle.py b/src/program/media/subtitle.py index a8a64799..0082771a 100644 --- a/src/program/media/subtitle.py +++ b/src/program/media/subtitle.py @@ -11,14 +11,14 @@ class Subtitle(db.Model): - __tablename__ = "Subtitle" + __tablename__ = "subtitle" - _id: Mapped[int] = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True) language: Mapped[str] = mapped_column(String) file: Mapped[str] = mapped_column(String, nullable=True) - parent_id: Mapped[int] = mapped_column(Integer, ForeignKey("MediaItem._id", ondelete="CASCADE")) - parent: Mapped["MediaItem"] = relationship("MediaItem", back_populates="subtitles") + parent_id: Mapped[int] = mapped_column(Integer, ForeignKey("profiledata.id", ondelete="CASCADE")) + parent: Mapped["ProfileData"] = relationship("ProfileData", back_populates="subtitles") __table_args__ = ( Index('ix_subtitle_language', 'language'), diff --git a/src/program/program.py b/src/program/program.py index df129973..75595479 100644 --- a/src/program/program.py +++ b/src/program/program.py @@ -15,7 +15,7 @@ from program.indexers.trakt import TraktIndexer from program.libraries import SymlinkLibrary from program.libraries.symlink import fix_broken_symlinks -from program.media.item import Episode, MediaItem, Movie, Season, Show +from program.media.item import Episode, MediaItem, Movie, Profile, Season, Show from program.media.state import States from program.post_processing import PostProcessing from program.scrapers import Scraping @@ -143,7 +143,8 @@ def start(self): self.executors = [] self.scheduler = BackgroundScheduler() - # self._schedule_services() + self.initialize_db() + self._schedule_services() # self._schedule_functions() super().start() @@ -156,7 +157,7 @@ def _retry_library(self) -> None: count = 0 with db.Session() as session: count = session.execute( - select(func.count(MediaItem._id)) + select(func.count(MediaItem.id)) .where(MediaItem.last_state.not_in([States.Completed, States.Unreleased])) .where(MediaItem.type.in_(["movie", "show"])) ).scalar_one() @@ -314,6 +315,13 @@ def stop(self): self.scheduler.shutdown(wait=False) logger.log("PROGRAM", "Riven has been stopped.") + def initialize_db(self): + with db.Session() as session: + existing = session.query(Profile).filter(Profile.name == settings_manager.settings.ranking.profile).first() + if not existing: + session.add(Profile(settings_manager.settings.ranking)) + session.commit() + def _enhance_item(self, item: MediaItem) -> MediaItem | None: try: enhanced_item = next(self.services[TraktIndexer].run(item, log_msg=False)) @@ -326,7 +334,7 @@ def _init_db_from_symlinks(self): """Initialize the database from symlinks.""" start_time = datetime.now() with db.Session() as session: - res = session.execute(select(func.count(MediaItem._id))).scalar_one() + res = session.execute(select(func.count(MediaItem.id))).scalar_one() added = [] errors = [] if res == 0: @@ -352,7 +360,7 @@ def _init_db_from_symlinks(self): added.append(enhanced_item.item_id) enhanced_item.store_state() session.add(enhanced_item) - log_message = f"Indexed IMDb Id: {enhanced_item.imdb_id} as {enhanced_item.type.title()}: {enhanced_item.log_string}" + log_message = f"Indexed IMDb Id: {enhanced_item.ids['imdb_id']} as {enhanced_item.type.title()}: {enhanced_item.log_string}" except Exception as e: logger.exception(f"Error processing {item.log_string}: {e}") finally: diff --git a/src/program/scrapers/jackett.py b/src/program/scrapers/jackett.py index eb80bbbf..c8a7e8bb 100644 --- a/src/program/scrapers/jackett.py +++ b/src/program/scrapers/jackett.py @@ -166,7 +166,7 @@ def _search_movie_indexer(self, item: MediaItem, indexer: JackettIndexer) -> Lis if indexer.movie_search_capabilities and "year" in indexer.movie_search_capabilities: if hasattr(item.aired_at, "year") and item.aired_at.year: params["year"] = item.aired_at.year if indexer.movie_search_capabilities and "imdbid" in indexer.movie_search_capabilities: - params["imdbid"] = item.imdb_id + params["imdbid"] = item.ids["imdb_id"] url = f"{self.settings.url}/api/v2.0/indexers/{indexer.id}/results/torznab/api" return self._fetch_results(url, params, indexer.title, "movie") @@ -190,7 +190,7 @@ def _search_series_indexer(self, item: MediaItem, indexer: JackettIndexer) -> Li if ep and indexer.tv_search_capabilities and "ep" in indexer.tv_search_capabilities: params["ep"] = ep if season and indexer.tv_search_capabilities and "season" in indexer.tv_search_capabilities: params["season"] = season if indexer.tv_search_capabilities and "imdbid" in indexer.tv_search_capabilities: - params["imdbid"] = item.imdb_id if isinstance(item, (Episode, Show)) else item.parent.imdb_id + params["imdbid"] = item.ids["imdb_id"] if isinstance(item, (Episode, Show)) else item.parent.ids["imdb_id"] url = f"{self.settings.url}/api/v2.0/indexers/{indexer.id}/results/torznab/api" return self._fetch_results(url, params, indexer.title, "series") diff --git a/src/program/scrapers/prowlarr.py b/src/program/scrapers/prowlarr.py index f3ce0be7..88dc52e5 100644 --- a/src/program/scrapers/prowlarr.py +++ b/src/program/scrapers/prowlarr.py @@ -174,7 +174,7 @@ def _search_movie_indexer(self, item: MediaItem, indexer: ProwlarrIndexer) -> Li if indexer.movie_search_capabilities and "year" in indexer.movie_search_capabilities: if hasattr(item.aired_at, "year") and item.aired_at.year: params["year"] = item.aired_at.year if indexer.movie_search_capabilities and "imdbid" in indexer.movie_search_capabilities: - params["imdbid"] = item.imdb_id + params["imdbid"] = item.ids["imdb_id"] url = f"{self.settings.url}/api/v1/indexer/{indexer.id}/newznab" return self._fetch_results(url, params, indexer.title, "movie") @@ -197,7 +197,7 @@ def _search_series_indexer(self, item: MediaItem, indexer: ProwlarrIndexer) -> L if ep and indexer.tv_search_capabilities and "ep" in indexer.tv_search_capabilities: params["ep"] = ep if season and indexer.tv_search_capabilities and "season" in indexer.tv_search_capabilities: params["season"] = season if indexer.tv_search_capabilities and "imdbid" in indexer.tv_search_capabilities: - params["imdbid"] = item.imdb_id if isinstance(item, [Episode, Show]) else item.parent.imdb_id + params["imdbid"] = item.ids["imdb_id"] if isinstance(item, [Episode, Show]) else item.parent.ids["imdb_id"] url = f"{self.settings.url}/api/v1/indexer/{indexer.id}/newznab" return self._fetch_results(url, params, indexer.title, "series") diff --git a/src/program/scrapers/shared.py b/src/program/scrapers/shared.py index ef98bb75..20d57df5 100644 --- a/src/program/scrapers/shared.py +++ b/src/program/scrapers/shared.py @@ -20,13 +20,13 @@ def _get_stremio_identifier(item: MediaItem) -> tuple[str | None, str, str]: """Get the stremio identifier for a media item based on its type.""" if isinstance(item, Show): - identifier, scrape_type, imdb_id = ":1:1", "series", item.imdb_id + identifier, scrape_type, imdb_id = ":1:1", "series", item.ids["imdb_id"] elif isinstance(item, Season): - identifier, scrape_type, imdb_id = f":{item.number}:1", "series", item.parent.imdb_id + identifier, scrape_type, imdb_id = f":{item.number}:1", "series", item.parent.ids["imdb_id"] elif isinstance(item, Episode): - identifier, scrape_type, imdb_id = f":{item.parent.number}:{item.number}", "series", item.parent.parent.imdb_id + identifier, scrape_type, imdb_id = f":{item.parent.number}:{item.number}", "series", item.parent.parent.ids["imdb_id"] elif isinstance(item, Movie): - identifier, scrape_type, imdb_id = None, "movie", item.imdb_id + identifier, scrape_type, imdb_id = None, "movie", item.ids["imdb_id"] else: return None, None, None return identifier, scrape_type, imdb_id diff --git a/src/program/settings/models.py b/src/program/settings/models.py index 0b9f0392..4ef4e5f3 100644 --- a/src/program/settings/models.py +++ b/src/program/settings/models.py @@ -351,6 +351,18 @@ class SubliminalConfig(Observable): } } +class RivenSettingsModel(SettingsModel): + def to_dict(self): + return { + "profile": self.profile, + "require": self.require, + "resolutions": self.resolutions, + "options": self.options, + "languages": self.languages, + # "custom_ranks": self.custom_ranks + } + + class PostProcessing(Observable): subliminal: SubliminalConfig = SubliminalConfig() @@ -366,7 +378,7 @@ class AppModel(Observable): downloaders: DownloadersModel = DownloadersModel() content: ContentModel = ContentModel() scraping: ScraperModel = ScraperModel() - ranking: RTNSettingsModel = RTNSettingsModel() + ranking: RivenSettingsModel = RivenSettingsModel() indexer: IndexerModel = IndexerModel() database: DatabaseModel = DatabaseModel() notifications: NotificationsModel = NotificationsModel() diff --git a/src/program/state_transition.py b/src/program/state_transition.py index eeb6018a..874d1759 100644 --- a/src/program/state_transition.py +++ b/src/program/state_transition.py @@ -22,10 +22,10 @@ def process_event(existing_item: MediaItem | None, emitted_by: Service, item: Me no_further_processing: ProcessedEvent = (None, None, []) items_to_submit = [] - source_services = (Overseerr, PlexWatchlist, Listrr, Mdblist, SymlinkLibrary, TraktContent) + source_services = (Overseerr, PlexWatchlist, Listrr, Mdblist, SymlinkLibrary, TraktContent, "ApiAdd") if emitted_by in source_services or item.state in [States.Requested]: next_service = TraktIndexer - if _imdb_exists_in_db(item.imdb_id) and item.last_state == States.Completed: + if _imdb_exists_in_db(item.ids["imdb_id"]) and item.last_state == States.Completed: logger.debug(f"Item {item.log_string} already exists in the database.") return no_further_processing if isinstance(item, Season): diff --git a/src/program/symlink.py b/src/program/symlink.py index 870d34f1..551a0a5e 100644 --- a/src/program/symlink.py +++ b/src/program/symlink.py @@ -208,23 +208,23 @@ def create_folder_path(base_path, *subfolders): return path if isinstance(item, Movie): - movie_folder = f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.imdb_id}}}" + movie_folder = f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.ids['imdb_id']}}}" destination_folder = create_folder_path(movie_path, movie_folder) item.set("update_folder", destination_folder) elif isinstance(item, Show): - folder_name_show = f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.imdb_id}}}" + folder_name_show = f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.ids['imdb_id']}}}" destination_folder = create_folder_path(show_path, folder_name_show) item.set("update_folder", destination_folder) elif isinstance(item, Season): show = item.parent - folder_name_show = f"{show.title.replace('/', '-')} ({show.aired_at.year}) {{imdb-{show.imdb_id}}}" + folder_name_show = f"{show.title.replace('/', '-')} ({show.aired_at.year}) {{imdb-{show.ids['imdb_id']}}}" show_path = create_folder_path(show_path, folder_name_show) folder_season_name = f"Season {str(item.number).zfill(2)}" destination_folder = create_folder_path(show_path, folder_season_name) item.set("update_folder", destination_folder) elif isinstance(item, Episode): show = item.parent.parent - folder_name_show = f"{show.title.replace('/', '-')} ({show.aired_at.year}) {{imdb-{show.imdb_id}}}" + folder_name_show = f"{show.title.replace('/', '-')} ({show.aired_at.year}) {{imdb-{show.ids['imdb_id']}}}" show_path = create_folder_path(show_path, folder_name_show) season = item.parent folder_season_name = f"Season {str(season.number).zfill(2)}" @@ -237,7 +237,7 @@ def _determine_file_name(self, item: Union[Movie, Episode]) -> str | None: """Determine the filename of the symlink.""" filename = None if isinstance(item, Movie): - filename = f"{item.title} ({item.aired_at.year}) " + "{imdb-" + item.imdb_id + "}" + filename = f"{item.title} ({item.aired_at.year}) " + "{imdb-" + item.ids['imdb_id'] + "}" elif isinstance(item, Season): showname = item.parent.title showyear = item.parent.aired_at.year @@ -264,10 +264,10 @@ def delete_item_symlinks(self, item: "MediaItem") -> bool: item_path = None if isinstance(item, Show): base_path = self.library_path_anime_shows if item.is_anime else self.library_path_shows - item_path = base_path / f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.imdb_id}}}" + item_path = base_path / f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.ids['imdb_id']}}}" elif isinstance(item, Movie): base_path = self.library_path_anime_movies if item.is_anime else self.library_path_movies - item_path = base_path / f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.imdb_id}}}" + item_path = base_path / f"{item.title.replace('/', '-')} ({item.aired_at.year}) {{imdb-{item.ids['imdb_id']}}}" return _delete_symlink(item, item_path) def _delete_symlink(item: Union[Movie, Show], item_path: Path) -> bool: diff --git a/src/tests/test_container.py b/src/tests/test_container.py index 64590340..e19cfb35 100644 --- a/src/tests/test_container.py +++ b/src/tests/test_container.py @@ -138,5 +138,5 @@ # # assert len(missing_items) == 4 # assert missing_items[next(iter(missing_items))].state == States.Unknown -# assert missing_items[next(iter(missing_items))].imdb_id == "tt1405406" +# assert missing_items[next(iter(missing_items))].ids["imdb_id"] == "tt1405406" # assert missing_items[next(iter(missing_items))].title == "Test Show" \ No newline at end of file diff --git a/src/tests/test_db_functions.py b/src/tests/test_db_functions.py index 4c8c076a..8b04e081 100644 --- a/src/tests/test_db_functions.py +++ b/src/tests/test_db_functions.py @@ -36,8 +36,8 @@ def test_reset_streams_for_mediaitem_with_no_streams(test_scoped_db_session): reset_streams(media_item) - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item._id).count() == 0 - assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item._id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item.id).count() == 0 + assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item.id).count() == 0 def test_add_new_mediaitem_with_multiple_streams_and_reset_streams(test_scoped_db_session): @@ -64,16 +64,16 @@ def test_add_new_mediaitem_with_multiple_streams_and_reset_streams(test_scoped_d test_scoped_db_session.add(stream2) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=media_item._id, child_id=stream1._id) - stream_relation2 = StreamRelation(parent_id=media_item._id, child_id=stream2._id) + stream_relation1 = StreamRelation(parent_id=media_item.id, child_id=stream1.id) + stream_relation2 = StreamRelation(parent_id=media_item.id, child_id=stream2.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.add(stream_relation2) test_scoped_db_session.commit() reset_streams(media_item) - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item._id).count() == 0 - assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item._id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item.id).count() == 0 + assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item.id).count() == 0 def test_blacklists_active_stream(test_scoped_db_session): media_item = MediaItem({"name":"New MediaItem"}) @@ -89,13 +89,13 @@ def test_blacklists_active_stream(test_scoped_db_session): test_scoped_db_session.add(media_item) test_scoped_db_session.add(stream) test_scoped_db_session.commit() - stream_relation = StreamRelation(parent_id=media_item._id, child_id=stream._id) + stream_relation = StreamRelation(parent_id=media_item.id, child_id=stream.id) test_scoped_db_session.add(stream_relation) test_scoped_db_session.commit() blacklist_stream(media_item, stream) - assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item._id, stream_id=stream._id).count() == 1 + assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item.id, stream_id=stream.id).count() == 1 def test_successfully_resets_streams(test_scoped_db_session): media_item = MediaItem({"name":"New MediaItem"}) @@ -124,16 +124,16 @@ def test_successfully_resets_streams(test_scoped_db_session): test_scoped_db_session.add(stream2) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=media_item._id, child_id=stream1._id) - stream_relation2 = StreamRelation(parent_id=media_item._id, child_id=stream2._id) + stream_relation1 = StreamRelation(parent_id=media_item.id, child_id=stream1.id) + stream_relation2 = StreamRelation(parent_id=media_item.id, child_id=stream2.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.add(stream_relation2) test_scoped_db_session.commit() reset_streams(media_item) - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item._id).count() == 0 - assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item._id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item.id).count() == 0 + assert test_scoped_db_session.query(StreamBlacklistRelation).filter_by(media_item_id=media_item.id).count() == 0 def test_delete_media_item_success(test_scoped_db_session): media_item = MediaItem({"name":"New MediaItem"}) @@ -151,19 +151,19 @@ def test_delete_media_item_success(test_scoped_db_session): test_scoped_db_session.add(stream1) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=media_item._id, child_id=stream1._id) + stream_relation1 = StreamRelation(parent_id=media_item.id, child_id=stream1.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.commit() - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item._id).count() == 1 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 1 + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item.id).count() == 1 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 1 delete_media_item(media_item) - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 0 + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 0 def test_delete_show_with_seasons_and_episodes_success(test_scoped_db_session): show = Show({"title": "New Show"}) @@ -172,14 +172,14 @@ def test_delete_show_with_seasons_and_episodes_success(test_scoped_db_session): test_scoped_db_session.commit() season1 = Season({"number": 1, "parent": show}) - season1.parent_id = show._id + season1.parent_id = show.id test_scoped_db_session.add(season1) test_scoped_db_session.commit() episode1 = Episode({"number": 1}) episode2 = Episode({"number": 2}) - episode1.parent_id = season1._id - episode2.parent_id = season1._id + episode1.parent_id = season1.id + episode2.parent_id = season1.id test_scoped_db_session.add(episode1) test_scoped_db_session.add(episode2) test_scoped_db_session.commit() @@ -195,23 +195,23 @@ def test_delete_show_with_seasons_and_episodes_success(test_scoped_db_session): test_scoped_db_session.add(stream1) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=show._id, child_id=stream1._id) + stream_relation1 = StreamRelation(parent_id=show.id, child_id=stream1.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.commit() - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 1 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 2 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 1 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 1 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 2 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 1 delete_media_item(show) - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 0 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 0 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 0 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 0 def test_delete_show_by_id_with_seasons_and_episodes_success(test_scoped_db_session): show = Show({"title": "New Show"}) @@ -220,14 +220,14 @@ def test_delete_show_by_id_with_seasons_and_episodes_success(test_scoped_db_sess test_scoped_db_session.commit() season1 = Season({"number": 1, "parent": show}) - season1.parent_id = show._id + season1.parent_id = show.id test_scoped_db_session.add(season1) test_scoped_db_session.commit() episode1 = Episode({"number": 1}) episode2 = Episode({"number": 2}) - episode1.parent_id = season1._id - episode2.parent_id = season1._id + episode1.parent_id = season1.id + episode2.parent_id = season1.id test_scoped_db_session.add(episode1) test_scoped_db_session.add(episode2) test_scoped_db_session.commit() @@ -243,23 +243,23 @@ def test_delete_show_by_id_with_seasons_and_episodes_success(test_scoped_db_sess test_scoped_db_session.add(stream1) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=show._id, child_id=stream1._id) + stream_relation1 = StreamRelation(parent_id=show.id, child_id=stream1.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.commit() - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 1 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 2 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 1 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 1 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 2 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 1 - delete_media_item_by_id(show._id) + delete_media_item_by_id(show.id) - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 0 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 0 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 0 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 0 def test_delete_show_by_item_id_with_seasons_and_episodes_success(test_scoped_db_session): show = Show({"title": "New Show"}) @@ -268,14 +268,14 @@ def test_delete_show_by_item_id_with_seasons_and_episodes_success(test_scoped_db test_scoped_db_session.commit() season1 = Season({"number": 1, "parent": show}) - season1.parent_id = show._id + season1.parent_id = show.id test_scoped_db_session.add(season1) test_scoped_db_session.commit() episode1 = Episode({"number": 1}) episode2 = Episode({"number": 2}) - episode1.parent_id = season1._id - episode2.parent_id = season1._id + episode1.parent_id = season1.id + episode2.parent_id = season1.id test_scoped_db_session.add(episode1) test_scoped_db_session.add(episode2) test_scoped_db_session.commit() @@ -291,23 +291,23 @@ def test_delete_show_by_item_id_with_seasons_and_episodes_success(test_scoped_db test_scoped_db_session.add(stream1) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=show._id, child_id=stream1._id) + stream_relation1 = StreamRelation(parent_id=show.id, child_id=stream1.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.commit() - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 1 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 2 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 1 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 1 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 2 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 1 delete_media_item_by_item_id(show.item_id) - assert test_scoped_db_session.query(Show).filter_by(_id=show._id).count() == 0 - assert test_scoped_db_session.query(Season).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 0 + assert test_scoped_db_session.query(Show).filter_by(_id=show.id).count() == 0 + assert test_scoped_db_session.query(Season).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Episode).filter_by(parent_id=season1.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=show.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 0 def test_delete_media_items_by_ids_success(test_scoped_db_session): media_item1 = MediaItem({"name": "New MediaItem 1"}) @@ -339,28 +339,28 @@ def test_delete_media_items_by_ids_success(test_scoped_db_session): test_scoped_db_session.add(stream2) test_scoped_db_session.commit() - stream_relation1 = StreamRelation(parent_id=media_item1._id, child_id=stream1._id) - stream_relation2 = StreamRelation(parent_id=media_item2._id, child_id=stream2._id) + stream_relation1 = StreamRelation(parent_id=media_item1.id, child_id=stream1.id) + stream_relation2 = StreamRelation(parent_id=media_item2.id, child_id=stream2.id) test_scoped_db_session.add(stream_relation1) test_scoped_db_session.add(stream_relation2) test_scoped_db_session.commit() - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item1._id).count() == 1 - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item2._id).count() == 1 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item1._id).count() == 1 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item2._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 1 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream2._id).count() == 1 - assert media_item1._id != media_item2._id + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item1.id).count() == 1 + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item2.id).count() == 1 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item1.id).count() == 1 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item2.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 1 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream2.id).count() == 1 + assert media_item1.id != media_item2.id - delete_media_items_by_ids([media_item1._id, media_item2._id]) + delete_media_items_by_ids([media_item1.id, media_item2.id]) - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item1._id).count() == 0 - assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item2._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item1._id).count() == 0 - assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item2._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream1._id).count() == 0 - assert test_scoped_db_session.query(Stream).filter_by(_id=stream2._id).count() == 0 + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item1.id).count() == 0 + assert test_scoped_db_session.query(MediaItem).filter_by(_id=media_item2.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item1.id).count() == 0 + assert test_scoped_db_session.query(StreamRelation).filter_by(parent_id=media_item2.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream1.id).count() == 0 + assert test_scoped_db_session.query(Stream).filter_by(_id=stream2.id).count() == 0 def test_get_media_items_by_ids_success(test_scoped_db_session): show = Show({"title": "Test Show"}) @@ -369,14 +369,14 @@ def test_get_media_items_by_ids_success(test_scoped_db_session): test_scoped_db_session.commit() season = Season({"number": 1, "parent": show}) - season.parent_id = show._id + season.parent_id = show.id test_scoped_db_session.add(season) test_scoped_db_session.commit() episode1 = Episode({"number": 1}) episode2 = Episode({"number": 2}) - episode1.parent_id = season._id - episode2.parent_id = season._id + episode1.parent_id = season.id + episode2.parent_id = season.id test_scoped_db_session.add(episode1) test_scoped_db_session.add(episode2) test_scoped_db_session.commit() @@ -386,12 +386,12 @@ def test_get_media_items_by_ids_success(test_scoped_db_session): test_scoped_db_session.add(movie) test_scoped_db_session.commit() - media_items = get_media_items_by_ids([show._id, season._id, episode1._id, episode2._id, movie._id]) + media_items = get_media_items_by_ids([show.id, season.id, episode1.id, episode2.id, movie.id]) assert len(media_items) == 5 - assert any(isinstance(item, Show) and item._id == show._id for item in media_items) - assert any(isinstance(item, Season) and item._id == season._id for item in media_items) - assert any(isinstance(item, Episode) and item._id == episode1._id for item in media_items) - assert any(isinstance(item, Episode) and item._id == episode2._id for item in media_items) - assert any(isinstance(item, Movie) and item._id == movie._id for item in media_items) \ No newline at end of file + assert any(isinstance(item, Show) and item.id == show.id for item in media_items) + assert any(isinstance(item, Season) and item.id == season.id for item in media_items) + assert any(isinstance(item, Episode) and item.id == episode1.id for item in media_items) + assert any(isinstance(item, Episode) and item.id == episode2.id for item in media_items) + assert any(isinstance(item, Movie) and item.id == movie.id for item in media_items) \ No newline at end of file diff --git a/src/tests/test_symlink_library.py b/src/tests/test_symlink_library.py index aa4f07ab..5636885c 100644 --- a/src/tests/test_symlink_library.py +++ b/src/tests/test_symlink_library.py @@ -95,6 +95,6 @@ def test_media_item_creation(symlink_library, fs): fs.create_file("/fake/library/movies/Top Gun (1986) tt0092099.mkv") items = list(symlink_library.run()) assert len(items) == 1, "Should create one media item." - assert items[0].imdb_id == "tt0092099", "Media item should have the correct IMDb ID." + assert items[0].ids["imdb_id"] == "tt0092099", "Media item should have the correct IMDb ID." assert isinstance(items[0], Movie), "The created item should be a Movie." assert items[0].state == States.Completed, "The created item should be in the Completed state." diff --git a/src/utils/event_manager.py b/src/utils/event_manager.py index 17e175b2..84d7f0d4 100644 --- a/src/utils/event_manager.py +++ b/src/utils/event_manager.py @@ -103,7 +103,7 @@ def remove_item_from_queue(self, item): """ with self.mutex: for event in self._queued_events: - if event.item.imdb_id == item.imdb_id: + if event.item.ids["imdb_id"] == item.ids["imdb_id"]: self._queued_events.remove(event) logger.debug(f"Removed {item.log_string} from the queue.") return @@ -128,7 +128,7 @@ def remove_item_from_running(self, item): """ with self.mutex: for event in self._running_events: - if event.item._id == item._id or (event.item.type == "mediaitem" and event.item.imdb_id == item.imdb_id): + if event.item.id == item.id or (event.item.type == "mediaitem" and event.item.ids["imdb_id"] == item.ids["imdb_id"]): self._running_events.remove(event) logger.debug(f"Removed {item.log_string} from the running events.") return @@ -202,8 +202,8 @@ def cancel_job(self, item, suppress_logs=False): self._futures.remove(future) # Clear from queued and running events - self._queued_events = [event for event in self._queued_events if event.item._id != item._id and event.item.imdb_id != item.imdb_id] - self._running_events = [event for event in self._running_events if event.item._id != item._id and event.item.imdb_id != item.imdb_id] + self._queued_events = [event for event in self._queued_events if event.item.id != item.id and event.item.ids["imdb_id"] != item.ids["imdb_id"]] + self._running_events = [event for event in self._running_events if event.item.id != item.id and event.item.ids["imdb_id"] != item.ids["imdb_id"]] logger.debug(f"Canceled jobs for item {item.log_string} and its children.") @@ -236,7 +236,7 @@ def _id_in_queue(self, _id): Returns: bool: True if the item is in the queue, False otherwise. """ - return any(event.item._id == _id for event in self._queued_events) + return any(event.item.id == _id for event in self._queued_events) def _id_in_running_events(self, _id): """ @@ -248,7 +248,7 @@ def _id_in_running_events(self, _id): Returns: bool: True if the item is in the running events, False otherwise. """ - return any(event.item._id == _id for event in self._running_events) + return any(event.item.id == _id for event in self._running_events) def _imdb_id_in_queue(self, imdb_id): """ @@ -260,7 +260,7 @@ def _imdb_id_in_queue(self, imdb_id): Returns: bool: True if the item is in the queue, False otherwise. """ - return any(event.item.imdb_id == imdb_id for event in self._queued_events) + return any(event.item.ids["imdb_id"] == imdb_id for event in self._queued_events) def _imdb_id_in_running_events(self, imdb_id): """ @@ -272,7 +272,7 @@ def _imdb_id_in_running_events(self, imdb_id): Returns: bool: True if the item is in the running events, False otherwise. """ - return any(event.item.imdb_id == imdb_id for event in self._running_events) + return any(event.item.ids["imdb_id"] == imdb_id for event in self._running_events) def add_event(self, event): """ @@ -288,22 +288,22 @@ def add_event(self, event): with db.Session() as session: item_id, related_ids = _get_item_ids(session, event.item) if item_id: - if self._id_in_queue(item_id): + if self.id_in_queue(item_id): logger.debug(f"Item {event.item.log_string} is already in the queue, skipping.") return False - if self._id_in_running_events(item_id): + if self.id_in_running_events(item_id): logger.debug(f"Item {event.item.log_string} is already running, skipping.") return False for related_id in related_ids: - if self._id_in_queue(related_id) or self._id_in_running_events(related_id): + if self.id_in_queue(related_id) or self.id_in_running_events(related_id): logger.debug(f"Child of {event.item.log_string} is already in the queue or running, skipping.") return False else: # Items that are not in the database - if self._imdb_id_in_queue(event.item.imdb_id): + if self._imdb_id_in_queue(event.item.ids["imdb_id"]): logger.debug(f"Item {event.item.log_string} is already in the queue, skipping.") return False - elif self._imdb_id_in_running_events(event.item.imdb_id): + elif self._imdb_id_in_running_events(event.item.ids["imdb_id"]): logger.debug(f"Item {event.item.log_string} is already running, skipping.") return False @@ -336,8 +336,8 @@ def get_event_updates(self): return { event_type.lower(): [ { - "item_id": event.item._id, - "imdb_id": event.item.imdb_id, + "item_id": event.item.id, + "imdb_id": event.item.ids["imdb_id"], "title": event.item.log_string, "type": event.item.type, "emitted_by": event.emitted_by if isinstance(event.emitted_by, str) else event.emitted_by.__name__, diff --git a/src/utils/websockets/manager.py b/src/utils/websockets/manager.py index 5d364772..e88ff0e7 100644 --- a/src/utils/websockets/manager.py +++ b/src/utils/websockets/manager.py @@ -31,7 +31,7 @@ async def _send_json(message: json, websocket: WebSocket): def send_event_update(events: list): event_types = ["Scraping", "Downloader", "Symlinker", "Updater", "PostProcessing"] - message = {event_type.lower(): [event.item._id for event in events if event.emitted_by == event_type] for event_type in event_types} + message = {event_type.lower(): [event.item.id for event in events if event.emitted_by == event_type] for event_type in event_types} broadcast({"type": "event_update", "message": message}) def send_health_update(status: str):