big upgrade UwU

This commit is contained in:
2026-07-27 15:16:01 +02:00
parent 9fb2bf0a74
commit 1ff979f1e5
30 changed files with 3304 additions and 183 deletions
+118
View File
@@ -0,0 +1,118 @@
"""Result objects describing what a download run did."""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
class FileStatus(str, Enum):
"""Outcome of a single file within a pack."""
DOWNLOADED = "downloaded"
SKIPPED_EXISTING = "skipped_existing"
FAILED = "failed"
PLANNED = "planned" # --dry-run only
@dataclass(frozen=True)
class FileOutcome:
"""What happened to one sticker file."""
path: Path
status: FileStatus
size: int = 0
error: str | None = None
@property
def name(self) -> str:
return self.path.name
@dataclass
class PackResult:
"""Aggregated outcome for one sticker pack."""
reference: str
name: str | None = None
title: str | None = None
directory: Path | None = None
total_stickers: int = 0
outcomes: list[FileOutcome] = field(default_factory=list)
error: str | None = None
@property
def downloaded(self) -> int:
return sum(1 for o in self.outcomes if o.status is FileStatus.DOWNLOADED)
@property
def skipped(self) -> int:
return sum(1 for o in self.outcomes if o.status is FileStatus.SKIPPED_EXISTING)
@property
def planned(self) -> int:
return sum(1 for o in self.outcomes if o.status is FileStatus.PLANNED)
@property
def failed(self) -> int:
return sum(1 for o in self.outcomes if o.status is FileStatus.FAILED)
@property
def bytes_written(self) -> int:
return sum(o.size for o in self.outcomes if o.status is FileStatus.DOWNLOADED)
@property
def ok(self) -> bool:
"""True when the pack was processed without a fatal or per-file error."""
return self.error is None and self.failed == 0
def summary(self) -> str:
if self.error is not None:
return f"failed: {self.error}"
parts = [f"{self.downloaded} downloaded"]
if self.planned:
parts.append(f"{self.planned} planned")
if self.skipped:
parts.append(f"{self.skipped} already present")
if self.failed:
parts.append(f"{self.failed} failed")
return ", ".join(parts)
@dataclass(frozen=True)
class RunTotals:
"""Totals across every pack in a run."""
packs: int
packs_ok: int
packs_failed: int
downloaded: int
skipped: int
planned: int
failed: int
bytes_written: int
@classmethod
def from_results(cls, results: list[PackResult]) -> RunTotals:
return cls(
packs=len(results),
packs_ok=sum(1 for r in results if r.ok),
packs_failed=sum(1 for r in results if not r.ok),
downloaded=sum(r.downloaded for r in results),
skipped=sum(r.skipped for r in results),
planned=sum(r.planned for r in results),
failed=sum(r.failed for r in results),
bytes_written=sum(r.bytes_written for r in results),
)
def format_size(num_bytes: int) -> str:
"""Human-readable byte count, e.g. ``1.4 MiB``."""
size = float(num_bytes)
for unit in ("B", "KiB", "MiB", "GiB"):
if size < 1024 or unit == "GiB":
precision = 0 if unit == "B" else 1
return f"{size:.{precision}f} {unit}"
size /= 1024
raise AssertionError("unreachable") # pragma: no cover