From 91e9fe7602a034c550aecb13a71fcb0832dd2798 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 8 Mar 2023 15:34:58 -0800 Subject: [PATCH] [FEATURE] Add summary to console output (#515) --- setup.cfg | 1 + src/ytdl_sub/cli/main.py | 78 +++++++++++++++++++ src/ytdl_sub/downloaders/downloader.py | 2 +- .../subscriptions/base_subscription.py | 36 +++++++++ src/ytdl_sub/utils/file_handler.py | 9 +++ .../enhanced_download_archive.py | 24 +++++- 6 files changed, 148 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3cb0c6b3..218ca950 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ packages=find: install_requires = yt-dlp==2023.3.4 argparse==1.4.0 + colorama==0.4.6, mergedeep==1.3.4 mediafile==0.10.1 PyYAML==6.0 diff --git a/src/ytdl_sub/cli/main.py b/src/ytdl_sub/cli/main.py index b5a1fa62..e21e54ea 100644 --- a/src/ytdl_sub/cli/main.py +++ b/src/ytdl_sub/cli/main.py @@ -1,4 +1,5 @@ import gc +import logging import os import sys from datetime import datetime @@ -7,6 +8,7 @@ from typing import List from typing import Optional from typing import Tuple +from colorama import Fore from yt_dlp.utils import sanitize_filename from ytdl_sub.cli.download_args_parser import DownloadArgsParser @@ -209,6 +211,79 @@ def _output_transaction_log( transaction_log_file.write(transaction_log_file_contents) +def _green(value: str) -> str: + return Fore.GREEN + value + Fore.RESET + + +def _red(value: str) -> str: + return Fore.RED + value + Fore.RESET + + +def _str_int(value: int) -> str: + if value > 0: + return f"+{value}" + if value < 0: + return f"-{value}" + return str(value) + + +def _color_int(value: int) -> str: + str_int = _str_int(value) + if value > 0: + return _green(str_int) + if value < 0: + return _red(str_int) + return str_int + + +def _output_summary(transaction_logs: List[Tuple[Subscription, FileHandlerTransactionLog]]): + summary: List[str] = [] + + # Initialize widths to 0 + width_sub_name: int = 0 + width_num_entries_added: int = 0 + width_num_entries_modified: int = 0 + width_num_entries_removed: int = 0 + width_num_entries: int = 0 + + # Calculate min width needed + for subscription, _ in transaction_logs: + width_sub_name = max(width_sub_name, len(subscription.name)) + width_num_entries_added = max( + width_num_entries_added, len(_str_int(subscription.num_entries_added)) + ) + width_num_entries_modified = max( + width_num_entries_modified, len(_str_int(subscription.num_entries_modified)) + ) + width_num_entries_removed = max( + width_num_entries_removed, len(_str_int(subscription.num_entries_removed * -1)) + ) + width_num_entries = max(width_num_entries, len(str(subscription.num_entries))) + + # Add spacing for aesthetics + width_sub_name += 4 + width_num_entries += 4 + + # Build the summary + for subscription, _ in transaction_logs: + num_entries_added = _color_int(subscription.num_entries_added) + num_entries_modified = _color_int(subscription.num_entries_modified) + num_entries_removed = _color_int(subscription.num_entries_removed * -1) + num_entries = str(subscription.num_entries) + status = _green("success") + + summary.append( + f"{subscription.name:<{width_sub_name}} " + f"{num_entries_added:>{width_num_entries_added}} " + f"{num_entries_modified:>{width_num_entries_modified}} " + f"{num_entries_removed:>{width_num_entries_removed}} " + f"{num_entries:>{width_num_entries}} " + f"{status}" + ) + + return "\n".join(summary) + + def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]: """ Entrypoint for ytdl-sub, without the error handling @@ -253,4 +328,7 @@ def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]: transaction_log_file_path=args.transaction_log, ) + # Hack to always show download summary, even if logs are set to quiet + logger.log(logging.WARNING, "Download Summary:\n%s", _output_summary(transaction_logs)) + return transaction_logs diff --git a/src/ytdl_sub/downloaders/downloader.py b/src/ytdl_sub/downloaders/downloader.py index 4d6d3dab..c20b6987 100644 --- a/src/ytdl_sub/downloaders/downloader.py +++ b/src/ytdl_sub/downloaders/downloader.py @@ -458,7 +458,7 @@ class Downloader(DownloadArchiver, Generic[DownloaderOptionsT], ABC): upload_date_idx = self._enhanced_download_archive.mapping.get_num_entries_with_upload_date( upload_date_standardized=entry.upload_date_standardized ) - download_idx = self._enhanced_download_archive.mapping.get_num_entries() + download_idx = self._enhanced_download_archive.num_entries entry.add_kwargs( { diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index d3a2cf06..143b9c31 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -135,6 +135,42 @@ class BaseSubscription(ABC): and self.downloader_class.supports_download_archive ) + @property + def num_entries_added(self) -> int: + """ + Returns + ------- + Number of entries added + """ + return self._enhanced_download_archive.num_entries_added + + @property + def num_entries_modified(self) -> int: + """ + Returns + ------- + Number of entries modified + """ + return self._enhanced_download_archive.num_entries_modified + + @property + def num_entries_removed(self) -> int: + """ + Returns + ------- + Number of entries removed + """ + return self._enhanced_download_archive.num_entries_removed + + @property + def num_entries(self) -> int: + """ + Returns + ------- + The number of entries + """ + return self._enhanced_download_archive.num_entries + def as_yaml(self) -> str: """ Returns diff --git a/src/ytdl_sub/utils/file_handler.py b/src/ytdl_sub/utils/file_handler.py index be5f1f58..654e87cb 100644 --- a/src/ytdl_sub/utils/file_handler.py +++ b/src/ytdl_sub/utils/file_handler.py @@ -416,7 +416,13 @@ class FileHandler: Optional. Metadata to record to the transaction log for this file copy_file Optional. If True, copy the file. Move otherwise + + Returns + ------- + bool + True if modified. False otherwise. """ + is_modified = False source_file_path = Path(self.working_directory) / file_name output_file_path = Path(self.output_directory) / output_file_name @@ -429,6 +435,7 @@ class FileHandler: self.file_handler_transaction_log.log_modified_file( file_name=output_file_name, file_metadata=file_metadata ) + is_modified = True # output file does not already exist, creates a new file else: self.file_handler_transaction_log.log_created_file( @@ -445,6 +452,8 @@ class FileHandler: elif self.dry_run and not copy_file: FileHandler.delete(source_file_path) + return is_modified + def delete_file_from_output_directory(self, file_name: str): """ Deletes a file from the output directory. All file deletions should use this function diff --git a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py index a5e1ce37..82e047b1 100644 --- a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +++ b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py @@ -363,6 +363,19 @@ class EnhancedDownloadArchive: self._logger = Logger.get(name=subscription_name) + self.num_entries_added: int = 0 + self.num_entries_modified: int = 0 + self.num_entries_removed: int = 0 + + @property + def num_entries(self) -> int: + """ + Returns + ------- + Total number of entries in the mapping + """ + return self.mapping.get_num_entries() + def reinitialize(self, dry_run: bool) -> "EnhancedDownloadArchive": """ Re-initialize the enhanced download archive for successive downloads w/the same @@ -544,6 +557,7 @@ class EnhancedDownloadArchive: self._file_handler.delete_file_from_output_directory(file_name=file_name) self.mapping.remove_entry(entry_id=uid) + self.num_entries_removed += 1 return self @@ -600,13 +614,21 @@ class EnhancedDownloadArchive: elif entry: self.mapping.add_entry(entry=entry, entry_file_path=output_file_name) - self._file_handler.move_file_to_output_directory( + is_modified = self._file_handler.move_file_to_output_directory( file_name=file_name, file_metadata=file_metadata, output_file_name=output_file_name, copy_file=copy_file, ) + # Determine if it's the entry file by seeing if the file_name to move matches the entry + # download file name + is_entry_file = entry and entry.get_download_file_name() == file_name + if is_entry_file and is_modified: + self.num_entries_modified += 1 + elif is_entry_file: + self.num_entries_added += 1 + def get_file_handler_transaction_log(self) -> FileHandlerTransactionLog: """ Returns