Files
siftlode/backend/alembic/versions/0061_dl_job_error_code.py
T
peter cdcffb8c91 feat(downloads): categorize download failures + a readable error dialog (S2)
Failed downloads stored the raw yt-dlp/ffmpeg text and dumped it as a truncated red
one-liner in the queue. Classify the failure into a category (unavailable / login_required /
geo_blocked / format_unavailable / drm / postprocess / source_missing / network / unknown)
and surface it properly:

- backend: app/downloads/errors.classify maps the raw message to a category code; the worker
  stores it in the new download_jobs.error_code (migration 0061) at both failure sites; resume
  clears it; the serializer exposes it. Raw text kept as technical detail.
- frontend: the queue row shows a clean localized reason + a "details" link opening a modal
  with the explanation, the raw text behind a disclosure, and a Retry only for transient
  categories (network/postprocess/unknown).
- tests: classify() unit coverage over verbatim yt-dlp/ffmpeg message shapes.
2026-07-28 01:40:22 +02:00

27 lines
1.1 KiB
Python

"""add download_jobs.error_code — a category for a failed download (S2)
The worker already stores the raw yt-dlp/ffmpeg failure text in `download_jobs.error`. This adds a
small category code (`unavailable` / `login_required` / `geo_blocked` / `format_unavailable` /
`drm` / `postprocess` / `source_missing` / `network` / `unknown`) computed by
`app.downloads.errors.classify`, so the UI can show a clean localized explanation and decide whether
a retry makes sense, keeping the raw text as a technical-detail disclosure. Nullable; only set on the
error path. Forward-only — an existing errored row simply has NULL until its next failure/retry.
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "0061_dl_job_error_code"
down_revision: Union[str, None] = "0060_plex_jsonb_null_normalize"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column("download_jobs", sa.Column("error_code", sa.String(length=24), nullable=True))
def downgrade() -> None:
op.drop_column("download_jobs", "error_code")