diff --git a/podcast_archiver/cli.py b/podcast_archiver/cli.py index bbaaf62..f813f41 100644 --- a/podcast_archiver/cli.py +++ b/podcast_archiver/cli.py @@ -33,6 +33,7 @@ "--filename-template", "--write-info-json", "--slugify", + "--dry-run", ], }, { @@ -181,6 +182,15 @@ def generate_default_config(ctx: click.Context, param: click.Parameter, value: b show_envvar=True, help=Settings.model_fields["quiet"].description, ) +@click.option( + "-n", + "--dry-run", + type=bool, + default=DEFAULT_SETTINGS.dry_run, + is_flag=True, + show_envvar=True, + help=Settings.model_fields["dry_run"].description, +) @click.option( "-C", "--concurrency", diff --git a/podcast_archiver/config.py b/podcast_archiver/config.py index 9f2abf4..df9e04c 100644 --- a/podcast_archiver/config.py +++ b/podcast_archiver/config.py @@ -81,6 +81,12 @@ class Settings(BaseModel): description="Print only minimal progress information. Errors will always be emitted.", ) + dry_run: bool = Field( + default=False, + alias="dry_run", + description="Do not download anything.", + ) + verbose: int = Field( default=0, alias="verbose", diff --git a/podcast_archiver/console.py b/podcast_archiver/console.py index a9463af..f8e36f7 100644 --- a/podcast_archiver/console.py +++ b/podcast_archiver/console.py @@ -1,3 +1,62 @@ -from rich.console import Console +from typing import TYPE_CHECKING + +from rich import progress, table +from rich.console import Console, RenderableType console = Console() + +if TYPE_CHECKING: + _MixinBase = progress.ProgressColumn +else: + _MixinBase = object + + +class HideableColumnMixin(_MixinBase): + def __call__(self, task: progress.Task) -> RenderableType: + speed = task.finished_speed or task.speed + if speed is None: + return "" + return super().__call__(task) + + +class HideableTimeRemainingColumn(HideableColumnMixin, progress.TimeRemainingColumn): + pass + + +class HideableTransferSpeedColumn(HideableColumnMixin, progress.TransferSpeedColumn): + pass + + +COMMON_PROGRESS_COLUMNS: list[progress.ProgressColumn] = [ + progress.SpinnerColumn(finished_text="[bar.finished]✔[/]"), + progress.TextColumn( + "{task.fields[date]:%Y-%m-%d}", + style="blue", + table_column=table.Column(width=len("2023-09-24")), + ), + progress.TextColumn( + "{task.description}", + style="progress.description", + table_column=table.Column(no_wrap=True, ratio=2), + ), + progress.BarColumn(bar_width=25), + progress.TaskProgressColumn(), + progress.DownloadColumn(), +] + +TRANSFER_COLUMNS: list[progress.ProgressColumn] = [ + HideableTimeRemainingColumn(), + HideableTransferSpeedColumn(), +] + + +def get_progress(console: Console, dry_run: bool, disable: bool) -> progress.Progress: + cols = COMMON_PROGRESS_COLUMNS + if dry_run: + cols += [progress.TextColumn("[bright_black]\\[dry-run][/]")] + else: + cols += TRANSFER_COLUMNS + + prog = progress.Progress(*cols, console=console, expand=True, disable=disable) + prog.live.vertical_overflow = "visible" + return prog diff --git a/podcast_archiver/download.py b/podcast_archiver/download.py index 101bc1f..97f7285 100644 --- a/podcast_archiver/download.py +++ b/podcast_archiver/download.py @@ -60,8 +60,9 @@ def __call__(self) -> DownloadResult: return DownloadResult.FAILED def run(self) -> DownloadResult: - self.target.parent.mkdir(parents=True, exist_ok=True) - self.write_info_json() + if not self.settings.dry_run: + self.target.parent.mkdir(parents=True, exist_ok=True) + self.write_info_json() if result := self.preflight_check(): return result @@ -75,6 +76,11 @@ def run(self) -> DownloadResult: total_size = int(response.headers.get("content-length", "0")) self.update_progress(total=total_size) + if self.settings.dry_run: + logger.info("Dry-run download of %s", self.target) + self.update_progress(total=total_size, completed=total_size) + return DownloadResult.COMPLETED_SUCCESSFULLY + with atomic_write(self.target, mode="wb") as fp: receive_complete = self.receive_data(fp, response) diff --git a/podcast_archiver/processor.py b/podcast_archiver/processor.py index f7add5c..82a32b8 100644 --- a/podcast_archiver/processor.py +++ b/podcast_archiver/processor.py @@ -9,23 +9,12 @@ from rich import progress as rich_progress from podcast_archiver.config import Settings -from podcast_archiver.console import console +from podcast_archiver.console import console, get_progress from podcast_archiver.download import DownloadJob from podcast_archiver.enums import DownloadResult, QueueCompletionType from podcast_archiver.logging import logger from podcast_archiver.models import Feed -PROGRESS_COLUMNS = ( - rich_progress.SpinnerColumn(finished_text="[bar.finished]✔[/]"), - rich_progress.TextColumn("[blue]{task.fields[date]:%Y-%m-%d}"), - rich_progress.TextColumn("[progress.description]{task.description}"), - rich_progress.BarColumn(bar_width=25), - rich_progress.TaskProgressColumn(), - rich_progress.TimeRemainingColumn(), - rich_progress.DownloadColumn(), - rich_progress.TransferSpeedColumn(), -) - @dataclass class ProcessingResult: @@ -44,12 +33,11 @@ class FeedProcessor: def __init__(self, settings: Settings) -> None: self.settings = settings self.pool_executor = ThreadPoolExecutor(max_workers=self.settings.concurrency) - self.progress = rich_progress.Progress( - *PROGRESS_COLUMNS, + self.progress = get_progress( console=console, disable=settings.verbose > 1 or settings.quiet, + dry_run=settings.dry_run, ) - # self.progress.live.vertical_overflow = "visible" self.stop_event = Event() def process(self, url: AnyHttpUrl) -> ProcessingResult: