From cc5e6bd85b331ce34367ca55645114fa9c65c3d4 Mon Sep 17 00:00:00 2001 From: RemiRigal Date: Mon, 2 May 2022 17:51:52 +0200 Subject: [PATCH] Add support for updated episodes (#34) * Add support for updated episodes * Update notification for updated episodes --- plex_auto_languages/alerts/status.py | 23 ++++++++----- plex_auto_languages/alerts/timeline.py | 2 +- plex_auto_languages/plex_server.py | 41 ++++++++++++---------- plex_auto_languages/plex_server_cache.py | 44 ++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 plex_auto_languages/plex_server_cache.py diff --git a/plex_auto_languages/alerts/status.py b/plex_auto_languages/alerts/status.py index f3e54a4..7f4281e 100644 --- a/plex_auto_languages/alerts/status.py +++ b/plex_auto_languages/alerts/status.py @@ -24,13 +24,12 @@ def process(self, plex: PlexServer): return logger.info("[Status] Library scan complete") - # Get all recently added episodes - for section in plex.get_all_show_sections(): - recent = section.searchEpisodes(filters={"addedAt>>": "5m"}) - if len(recent) == 0: - continue - logger.debug(f"[Status] Found {len(recent)} newly added episode(s) in section {section}") - for item in recent: + added, updated = plex.cache.refresh_library_cache() + + # Process recently added episodes + if len(added) > 0: + logger.debug(f"[Status] Found {len(added)} newly added episode(s)") + for item in added: # Check if the item has already been processed if item.key in plex.cache.newly_added and plex.cache.newly_added[item.key] == item.addedAt: continue @@ -38,4 +37,12 @@ def process(self, plex: PlexServer): # Change tracks for all users logger.info(f"[Status] Processing newly added episode {plex.get_episode_short_name(item)}") - plex.process_new_episode(item.key) + plex.process_new_or_updated_episode(item.key) + + # Process updated episodes + if len(updated) > 0: + logger.debug(f"[Status] Found {len(updated)} updated episode(s)") + for item in updated: + # Change tracks for all users + logger.info(f"[Status] Processing updated episode {plex.get_episode_short_name(item)}") + plex.process_new_or_updated_episode(item.key, new=False) diff --git a/plex_auto_languages/alerts/timeline.py b/plex_auto_languages/alerts/timeline.py index d313f8b..7a5dcd3 100644 --- a/plex_auto_languages/alerts/timeline.py +++ b/plex_auto_languages/alerts/timeline.py @@ -60,4 +60,4 @@ def process(self, plex: PlexServer): # Change tracks for all users logger.info(f"[Timeline] Processing newly added episode {plex.get_episode_short_name(item)}") - plex.process_new_episode(self.item_id) + plex.process_new_or_updated_episode(self.item_id) diff --git a/plex_auto_languages/plex_server.py b/plex_auto_languages/plex_server.py index 02a018e..baed402 100644 --- a/plex_auto_languages/plex_server.py +++ b/plex_auto_languages/plex_server.py @@ -13,6 +13,7 @@ from plex_auto_languages.plex_alert_handler import PlexAlertHandler from plex_auto_languages.track_changes import TrackChanges from plex_auto_languages.utils.notifier import Notifier +from plex_auto_languages.plex_server_cache import PlexServerCache logger = get_logger() @@ -24,12 +25,29 @@ def __init__(self, url: str, token: str): self._plex_url = url self._plex = BasePlexServer(url, token) + @property + def unique_id(self): + return self._plex.machineIdentifier + def fetch_item(self, item_id: Union[str, int]): try: return self._plex.fetchItem(item_id) except NotFound: return None + def episodes(self): + return self._plex.library.all(libtype="episode") + + def get_recently_added_episodes(self, minutes: int): + episodes = [] + for section in self.get_show_sections(): + recent = section.searchEpisodes(filters={"addedAt>>": f"{minutes}m"}) + episodes.extend(recent) + return episodes + + def get_show_sections(self): + return [s for s in self._plex.library.sections() if isinstance(s, ShowSection)] + @staticmethod def get_last_watched_or_first_episode(show: Show): watched_episodes = show.watched() @@ -67,7 +85,7 @@ def __init__(self, url: str, token: str, notifier: Notifier, config: Configurati logger.info(f"Successfully connected as user '{self.username}' (id: {self.user_id})") self._alert_handler = None self._alert_listener = None - self.cache = PlexCache() + self.cache = PlexServerCache(self) @property def is_alive(self): @@ -118,7 +136,7 @@ def get_user_by_id(self, user_id: Union[int, str]): return None return matching_users[0] - def process_new_episode(self, item_id: Union[int, str]): + def process_new_or_updated_episode(self, item_id: Union[int, str], new: bool = True): for user_id in self.get_all_user_ids(): # Switch to the user's Plex instance user_plex = self.get_plex_instance_of_user(user_id) @@ -140,7 +158,7 @@ def process_new_episode(self, item_id: Union[int, str]): if user is None: return self.change_default_tracks_if_needed(user.name, reference, episodes=[user_item], notify=False) - self.notify_new_episode(self.fetch_item(item_id)) + self.notify_updated_or_new_episode(self.fetch_item(item_id), new) def change_default_tracks_if_needed(self, username: str, episode: Episode, episodes: List[Episode] = None, notify: bool = True): @@ -173,8 +191,8 @@ def notify_changes(self, track_changes: TrackChanges): title = f"PlexAutoLanguages - {track_changes.reference_name}" self.notifier.notify_user(title, track_changes.description, track_changes.username) - def notify_new_episode(self, episode: Episode): - title = "PlexAutoLanguages - New episode" + def notify_updated_or_new_episode(self, episode: Episode, new: bool): + title = f"PlexAutoLanguages - {'New' if new else 'Updated'} episode" message = ( f"Episode: {self.get_episode_short_name(episode)}\n" f"Updated language for all users" @@ -185,9 +203,6 @@ def notify_new_episode(self, episode: Episode): return self.notifier.notify(title, message) - def get_all_show_sections(self): - return [s for s in self._plex.library.sections() if isinstance(s, ShowSection)] - def start_deep_analysis(self): min_date = datetime.now() - timedelta(days=1) history = self._plex.history(mindate=min_date) @@ -197,13 +212,3 @@ def start_deep_analysis(self): continue episode.reload() self.change_default_tracks_if_needed(user.name, episode) - - -class PlexCache(): - - def __init__(self): - self.session_states = {} # session_key: session_state - self.default_streams = {} # item_key: (audio_stream_id, substitle_stream_id) - self.user_clients = {} # client_identifier: user_id - self.newly_added = {} # episode_id: added_at - self.recent_activities = {} # (user_id, item_id): timestamp diff --git a/plex_auto_languages/plex_server_cache.py b/plex_auto_languages/plex_server_cache.py new file mode 100644 index 0000000..94f0bd7 --- /dev/null +++ b/plex_auto_languages/plex_server_cache.py @@ -0,0 +1,44 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +from plex_auto_languages.utils.logger import get_logger + +if TYPE_CHECKING: + from plex_auto_languages.plex_server import PlexServer + + +logger = get_logger() + + +class PlexServerCache(): + + def __init__(self, plex: PlexServer): + self._plex = plex + # Alerts cache + self.session_states = {} # session_key: session_state + self.default_streams = {} # item_key: (audio_stream_id, substitle_stream_id) + self.user_clients = {} # client_identifier: user_id + self.newly_added = {} # episode_id: added_at + self.recent_activities = {} # (user_id, item_id): timestamp + # Library cache + self.episode_parts = {} + # Initialization + logger.info("Scanning all episodes from the library, this action can take a few seconds") + self.refresh_library_cache() + logger.info(f"Scanned {len(self.episode_parts)} episodes from the library") + + def refresh_library_cache(self): + logger.debug("[Cache] Refreshing library cache") + added = [] + updated = [] + new_episode_parts = {} + for episode in self._plex.episodes(): + part_list = new_episode_parts.setdefault(episode.key, []) + for part in episode.iterParts(): + part_list.append(part.key) + if episode.key in self.episode_parts and set(self.episode_parts[episode.key]) != set(part_list): + updated.append(episode) + elif episode.key not in self.episode_parts: + added.append(episode) + self.episode_parts = new_episode_parts + return added, updated