diff --git a/modules/builder.py b/modules/builder.py index a804abd7..ad0634e9 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1660,6 +1660,7 @@ class CollectionBuilder: total = len(self.added_items) spacing = len(str(total)) * 2 + 1 amount_added = 0 + amount_unchanged = 0 playlist_adds = [] for i, item in enumerate(self.added_items, 1): current_operation = "=" if item in collection_items else "+" @@ -1667,6 +1668,7 @@ class CollectionBuilder: logger.info(util.adjust_space(f"{number_text:>{spacing}} | {name} {self.Type} | {current_operation} | {util.item_title(item)}")) if item in collection_items: self.plex_map[item.ratingKey] = None + amount_unchanged += 1 else: if self.playlist: playlist_adds.append(item) @@ -1690,7 +1692,7 @@ class CollectionBuilder: util.print_end() logger.info("") logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed") - return amount_added + return amount_added, amount_unchanged def sync_collection(self): amount_removed = 0 @@ -2326,6 +2328,7 @@ class CollectionBuilder: name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection) self.created = False rating_keys = [] + amount_added = 0 self.notification_additions = [] for mm in self.run_again_movies: if mm in self.library.movie_map: @@ -2345,6 +2348,7 @@ class CollectionBuilder: logger.info(f"{name} {self.Type} | = | {util.item_title(current)}") else: self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection) + amount_added += 1 logger.info(f"{name} {self.Type} | + | {util.item_title(current)}") if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map: add_id = self.library.movie_rating_key_map[current.ratingKey] @@ -2383,3 +2387,5 @@ class CollectionBuilder: if self.details["show_missing"] is True: logger.info(f"{name} {self.Type} | ? | {title} (TVDb: {missing_id})") logger.info(f"{len(self.run_again_shows)} Show{'s' if len(self.run_again_shows) > 1 else ''} Missing") + + return amount_added \ No newline at end of file diff --git a/modules/library.py b/modules/library.py index 6c226492..0be02c8a 100644 --- a/modules/library.py +++ b/modules/library.py @@ -80,6 +80,8 @@ class Library(ABC): self.clean_bundles = params["plex"]["clean_bundles"] # TODO: Here or just in Plex? self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex? self.optimize = params["plex"]["optimize"] # TODO: Here or just in Plex? + self.stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} + self.status = {} self.tmdb_library_operation = self.assets_for_all or self.mass_genre_update or self.mass_audience_rating_update \ or self.mass_critic_rating_update or self.mass_trakt_rating_update \ diff --git a/modules/webhooks.py b/modules/webhooks.py index 6bf56e28..ace4602c 100644 --- a/modules/webhooks.py +++ b/modules/webhooks.py @@ -1,6 +1,6 @@ import logging from json import JSONDecodeError - +from modules import util from modules.util import Failed logger = logging.getLogger("Plex Meta Manager") @@ -16,6 +16,7 @@ class Webhooks: def _request(self, webhooks, json): if self.config.trace_mode: + util.separator("Webhooks", space=False, border=False) logger.debug("") logger.debug(f"JSON: {json}") for webhook in list(set(webhooks)): diff --git a/plex_meta_manager.py b/plex_meta_manager.py index d1a62cfb..a3200881 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -72,7 +72,6 @@ divider = get_arg("PMM_DIVIDER", args.divider) screen_width = get_arg("PMM_WIDTH", args.width, arg_int=True) debug = get_arg("PMM_DEBUG", args.debug, arg_bool=True) trace = get_arg("PMM_TRACE", args.trace, arg_bool=True) -stats = {} util.separating_character = divider[0] @@ -163,8 +162,7 @@ def start(attrs): logger.debug("") util.separator(f"Starting {start_type}Run") config = None - global stats - stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "removed": 0, "radarr": 0, "sonarr": 0} + stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} try: config = ConfigFile(default_dir, attrs, read_only_config) except Exception as e: @@ -172,7 +170,7 @@ def start(attrs): util.print_multiline(e, critical=True) else: try: - update_libraries(config) + stats = update_libraries(config) except Exception as e: config.notify(e) util.print_stacktrace() @@ -190,7 +188,6 @@ def start(attrs): logger.removeHandler(file_handler) def update_libraries(config): - global stats for library in config.libraries: if library.skip_library: logger.info("") @@ -278,6 +275,8 @@ def update_libraries(config): util.print_stacktrace() util.print_multiline(e, critical=True) + playlist_status = {} + playlist_stats = {} if config.playlist_files: os.makedirs(os.path.join(default_dir, "logs", "playlists"), exist_ok=True) pf_file_logger = os.path.join(default_dir, "logs", "playlists", "playlists.log") @@ -287,7 +286,7 @@ def update_libraries(config): if should_roll_over: playlists_handler.doRollover() logger.addHandler(playlists_handler) - run_playlists(config) + playlist_status, playlist_stats = run_playlists(config) logger.removeHandler(playlists_handler) has_run_again = False @@ -296,6 +295,7 @@ def update_libraries(config): has_run_again = True break + amount_added = 0 if has_run_again and not library_only: logger.info("") util.separator("Run Again") @@ -320,10 +320,10 @@ def update_libraries(config): library.map_guids() for builder in library.run_again: logger.info("") - util.separator(f"{builder.name} Collection") + util.separator(f"{builder.name} Collection in {library.name}") logger.info("") try: - builder.run_collections_again() + amount_added += builder.run_collections_again() except Failed as e: library.notify(e, collection=builder.name, critical=False) util.print_stacktrace() @@ -345,6 +345,55 @@ def update_libraries(config): if library.optimize: library.query(library.PlexServer.library.optimize) + longest = 20 + for library in config.libraries: + for title in library.status: + if len(title) > longest: + longest = len(title) + if playlist_status: + for title in playlist_status: + if len(title) > longest: + longest = len(title) + def print_status(section, status): + logger.info("") + util.separator(f"{section} Summary", space=False, border=False) + logger.info("") + for name, data in status.items(): + logger.info(f"{name:<{longest}} | {data['status']:<13} | +{data['added']} ={data['unchanged']} -{data['removed']}") + if data["errors"]: + for error in data["errors"]: + util.print_multiline(error, info=True) + logger.info("") + + util.separator("Summary") + for library in config.libraries: + print_status(library.name, library.status) + if playlist_status: + print_status("Playlists", playlist_status) + + stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} + stats["added"] += amount_added + for library in config.libraries: + stats["created"] += library.stats["created"] + stats["modified"] += library.stats["modified"] + stats["deleted"] += library.stats["deleted"] + stats["added"] += library.stats["added"] + stats["unchanged"] += library.stats["unchanged"] + stats["removed"] += library.stats["removed"] + stats["radarr"] += library.stats["radarr"] + stats["sonarr"] += library.stats["sonarr"] + if playlist_stats: + stats["created"] += playlist_stats["created"] + stats["modified"] += playlist_stats["modified"] + stats["deleted"] += playlist_stats["deleted"] + stats["added"] += playlist_stats["added"] + stats["unchanged"] += playlist_stats["unchanged"] + stats["removed"] += playlist_stats["removed"] + stats["radarr"] += playlist_stats["radarr"] + stats["sonarr"] += playlist_stats["sonarr"] + + return stats + def library_operations(config, library): logger.info("") util.separator(f"{library.name} Library Operations") @@ -614,7 +663,6 @@ def library_operations(config, library): library.find_assets(col) def run_collection(config, library, metadata, requested_collections): - global stats logger.info("") for mapping_name, collection_attrs in requested_collections.items(): collection_start = datetime.now() @@ -653,6 +701,7 @@ def run_collection(config, library, metadata, requested_collections): if should_roll_over: collection_handler.doRollover() logger.addHandler(collection_handler) + library.status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} try: util.separator(f"{mapping_name} Collection in {library.name}") @@ -692,12 +741,16 @@ def run_collection(config, library, metadata, requested_collections): builder.find_rating_keys() if len(builder.added_items) >= builder.minimum and builder.build_collection: - items_added = builder.add_to_collection() - stats["added"] += items_added + items_added, items_unchanged = builder.add_to_collection() + library.stats["added"] += items_added + library.status[mapping_name]["added"] = items_added + library.stats["unchanged"] += items_unchanged + library.status[mapping_name]["unchanged"] = items_unchanged items_removed = 0 if builder.sync: items_removed = builder.sync_collection() - stats["removed"] += items_removed + library.stats["removed"] += items_removed + library.status[mapping_name]["removed"] = items_removed elif len(builder.added_items) < builder.minimum and builder.build_collection: logger.info("") logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") @@ -709,17 +762,21 @@ def run_collection(config, library, metadata, requested_collections): if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): radarr_add, sonarr_add = builder.run_missing() - stats["radarr"] += radarr_add - stats["sonarr"] += sonarr_add + library.stats["radarr"] += radarr_add + library.status[mapping_name]["radarr"] += radarr_add + library.stats["sonarr"] += sonarr_add + library.status[mapping_name]["sonarr"] += sonarr_add run_item_details = True if valid and builder.build_collection and (builder.builders or builder.smart_url): try: builder.load_collection() if builder.created: - stats["created"] += 1 + library.stats["created"] += 1 + library.status[mapping_name]["created"] = True elif items_added > 0 or items_removed > 0: - stats["modified"] += 1 + library.stats["modified"] += 1 + library.status[mapping_name]["modified"] = True except Failed: util.print_stacktrace() run_item_details = False @@ -729,7 +786,8 @@ def run_collection(config, library, metadata, requested_collections): builder.update_details() if builder.deleted: - stats["deleted"] += 1 + library.stats["deleted"] += 1 + library.status[mapping_name]["deleted"] = True if builder.server_preroll is not None: library.set_server_preroll(builder.server_preroll) @@ -754,21 +812,36 @@ def run_collection(config, library, metadata, requested_collections): if builder.run_again and (len(builder.run_again_movies) > 0 or len(builder.run_again_shows) > 0): library.run_again.append(builder) + if library.status[mapping_name]["created"]: + library.status[mapping_name]["status"] = "Created" + elif library.status[mapping_name]["deleted"]: + library.status[mapping_name]["status"] = "Deleted" + elif library.status[mapping_name]["modified"]: + library.status[mapping_name]["status"] = "Modified" + else: + library.status[mapping_name]["status"] = "Unchanged" except NotScheduled as e: util.print_multiline(e, info=True) + library.status[mapping_name]["status"] = "Not Scheduled" except Failed as e: library.notify(e, collection=mapping_name) util.print_stacktrace() util.print_multiline(e, error=True) + library.status[mapping_name]["status"] = "PMM Failure" + library.status[mapping_name]["errors"].append(e) except Exception as e: library.notify(f"Unknown Error: {e}", collection=mapping_name) util.print_stacktrace() logger.error(f"Unknown Error: {e}") + library.status[mapping_name]["status"] = "Unknown Error" + library.status[mapping_name]["errors"].append(e) logger.info("") util.separator(f"Finished {mapping_name} Collection\nCollection Run Time: {str(datetime.now() - collection_start).split('.')[0]}") logger.removeHandler(collection_handler) def run_playlists(config): + stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} + status = {} logger.info("") util.separator("Playlists") logger.info("") @@ -805,6 +878,7 @@ def run_playlists(config): if should_roll_over: playlist_handler.doRollover() logger.addHandler(playlist_handler) + status[mapping_name] = {"status": "", "errors": [], "created": False, "modified": False, "deleted": False, "added": 0, "unchanged": 0, "removed": 0, "radarr": 0, "sonarr": 0} server_name = None library_names = None try: @@ -1004,12 +1078,16 @@ def run_playlists(config): builder.filter_and_save_items(items) if len(builder.added_items) >= builder.minimum: - items_added = builder.add_to_collection() + items_added, items_unchanged = builder.add_to_collection() stats["added"] += items_added + status[mapping_name]["added"] += items_added + stats["unchanged"] += items_unchanged + status[mapping_name]["unchanged"] += items_unchanged items_removed = 0 if builder.sync: items_removed = builder.sync_collection() stats["removed"] += items_removed + status[mapping_name]["removed"] += items_removed elif len(builder.added_items) < builder.minimum: logger.info("") logger.info(f"Playlist Minimum: {builder.minimum} not met for {mapping_name} Playlist") @@ -1022,15 +1100,19 @@ def run_playlists(config): if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): radarr_add, sonarr_add = builder.run_missing() stats["radarr"] += radarr_add + status[mapping_name]["radarr"] += radarr_add stats["sonarr"] += sonarr_add + status[mapping_name]["sonarr"] += sonarr_add run_item_details = True try: builder.load_collection() if builder.created: stats["created"] += 1 + status[mapping_name]["created"] = True elif items_added > 0 or items_removed > 0: stats["modified"] += 1 + status[mapping_name]["modified"] = True except Failed: util.print_stacktrace() run_item_details = False @@ -1041,6 +1123,7 @@ def run_playlists(config): if builder.deleted: stats["deleted"] += 1 + status[mapping_name]["deleted"] = True if valid and run_item_details and builder.builders and (builder.item_details or builder.custom_sort): try: @@ -1061,19 +1144,23 @@ def run_playlists(config): except NotScheduled as e: util.print_multiline(e, info=True) + status[mapping_name]["status"] = "Not Scheduled" except Failed as e: config.notify(e, server=server_name, library=library_names, playlist=mapping_name) util.print_stacktrace() util.print_multiline(e, error=True) + status[mapping_name]["status"] = "PMM Failure" + status[mapping_name]["errors"].append(e) except Exception as e: config.notify(f"Unknown Error: {e}", server=server_name, library=library_names, playlist=mapping_name) util.print_stacktrace() logger.error(f"Unknown Error: {e}") + status[mapping_name]["status"] = "Unknown Error" + status[mapping_name]["errors"].append(e) logger.info("") - util.separator( - f"Finished {mapping_name} Playlist\nPlaylist Run Time: {str(datetime.now() - playlist_start).split('.')[0]}") + util.separator(f"Finished {mapping_name} Playlist\nPlaylist Run Time: {str(datetime.now() - playlist_start).split('.')[0]}") logger.removeHandler(playlist_handler) - + return status, stats try: if run or test or collections or libraries or resume: