From 87372e41bef49f1b3d8def82ef9329ab1469f443 Mon Sep 17 00:00:00 2001 From: Benexl Date: Tue, 29 Jul 2025 12:36:17 +0300 Subject: [PATCH] feat(media-actions-menu): bulk media list actions --- .../interactive/menu/media/media_actions.py | 80 ++++++++++++++++++- fastanime/cli/service/feedback/service.py | 18 ++++- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/fastanime/cli/interactive/menu/media/media_actions.py b/fastanime/cli/interactive/menu/media/media_actions.py index 923748f..cd0f2f7 100644 --- a/fastanime/cli/interactive/menu/media/media_actions.py +++ b/fastanime/cli/interactive/menu/media/media_actions.py @@ -73,6 +73,9 @@ def media_actions(ctx: Context, state: State) -> State | InternalDirective: f"{'➕ ' if icons else ''}Add/Update List": _manage_user_media_list( ctx, state ), + f"{'➕ ' if icons else ''}Add/Update List (Bulk)": _manage_user_media_list_in_bulk( + ctx, state + ), f"{'⭐ ' if icons else ''}Score Anime": _score_anime(ctx, state), f"{'ℹ️ ' if icons else ''}View Info": _view_info(ctx, state), f"{'📀 ' if icons else ''}Change Provider (Current: {ctx.config.general.provider.value.upper()})": _change_provider( @@ -202,7 +205,7 @@ def _manage_user_media_list(ctx: Context, state: State) -> MenuAction: return InternalDirective.RELOAD status = ctx.selector.choose( - "Select list status:", choices=[t.value for t in UserMediaListStatus] + "Select list status", choices=[t.value for t in UserMediaListStatus] ) if status: # local @@ -212,11 +215,84 @@ def _manage_user_media_list(ctx: Context, state: State) -> MenuAction: status=UserMediaListStatus(status), ) # remote - ctx.media_api.update_list_entry( + if not ctx.media_api.update_list_entry( UpdateUserMediaListEntryParams( media_item.id, status=UserMediaListStatus(status) ) + ): + print(f"Failed to update {media_item.title.english}") + return InternalDirective.RELOAD + + return action + + +def _manage_user_media_list_in_bulk(ctx: Context, state: State) -> MenuAction: + def action(): + feedback = ctx.feedback + search_result = state.media_api.search_result + + if not search_result: + return InternalDirective.RELOAD + + if not ctx.media_api.is_authenticated(): + feedback.warning( + "You are not authenticated", ) + return InternalDirective.RELOAD + + choice_map: Dict[str, MediaItem] = { + item.title.english: item for item in search_result.values() + } + preview_command = None + if ctx.config.general.preview != "none": + from ....utils.preview import create_preview_context + + with create_preview_context() as preview_ctx: + preview_command = preview_ctx.get_anime_preview( + list(choice_map.values()), + list(choice_map.keys()), + ctx.config, + ) + selected_titles = ctx.selector.choose_multiple( + "Select anime to download", + list(choice_map.keys()), + preview=preview_command, + ) + else: + selected_titles = ctx.selector.choose_multiple( + "Select anime to download", + list(choice_map.keys()), + ) + if not selected_titles: + feedback.warning("No anime selected. Aborting download.") + return InternalDirective.RELOAD + anime_to_update_status = [choice_map[title] for title in selected_titles] + + status = ctx.selector.choose( + "Select list status", choices=[t.value for t in UserMediaListStatus] + ) + if not status: + feedback.warning("No status selected. Aborting bulk action.") + return InternalDirective.RELOAD + with feedback.progress( + "Updating media list...", total=len(anime_to_update_status) + ) as (task_id, progress): + for media_item in anime_to_update_status: + feedback.info(f"Updating media status for {media_item.title.english}") + ctx.media_registry.update_media_index_entry( + media_id=media_item.id, + media_item=media_item, + status=UserMediaListStatus(status), + ) + # remote + + if not ctx.media_api.update_list_entry( + UpdateUserMediaListEntryParams( + media_item.id, status=UserMediaListStatus(status) + ) + ): + print(f"Failed to update {media_item.title.english}") + progress.update(task_id, advance=1) return InternalDirective.RELOAD return action diff --git a/fastanime/cli/service/feedback/service.py b/fastanime/cli/service/feedback/service.py index 6c4705a..eac2b12 100644 --- a/fastanime/cli/service/feedback/service.py +++ b/fastanime/cli/service/feedback/service.py @@ -3,7 +3,13 @@ from typing import Optional import click from rich.console import Console -from rich.progress import Progress, SpinnerColumn, TextColumn +from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TaskProgressColumn, + TextColumn, +) console = Console() @@ -60,6 +66,8 @@ class FeedbackService: def progress( self, message: str, + total: Optional[float] = None, + transient: bool = True, success_msg: Optional[str] = None, error_msg: Optional[str] = None, ): @@ -67,12 +75,14 @@ class FeedbackService: with Progress( SpinnerColumn(), TextColumn(f"[cyan]{message}..."), - transient=True, + BarColumn(), + TaskProgressColumn(), + transient=transient, console=console, ) as progress: - progress.add_task("", total=None) + task_id = progress.add_task("", total=total) try: - yield + yield task_id, progress if success_msg: self.success(success_msg) except Exception as e: