chore(downloads): C3-C6 — DRY the source-URL, filename, and serialize helpers
- C3: `_reference_url` (downloads.py) and public.py's inline source-URL block were the same rule → extract `service.reference_url(job, asset)`; both surfaces now share it so the "downloaded from" link can't drift between them. - C4: Content-Disposition filename derivation (ext pick + doubled-ext strip + join) was duplicated in download_file and watch_file → extract `storage.download_filename(display_name, container, path)`. - C5: inline the one-line `_clean_basename` passthrough (folded into C4). - C6: the `db.get(MediaAsset, job.asset_id) if job.asset_id else None; _serialize(...)` resolve-then-serialize dance was repeated across 6 single-job handlers → fold into `_serialize_job(db, job)`. (File-serving handlers that use the asset for their own checks keep their explicit resolve.) Behavior-neutral; ruff/parse clean, localdev boots, downloads routes load.
This commit is contained in:
parent
2a44db04d8
commit
cac5526399
5 changed files with 44 additions and 49 deletions
|
|
@ -67,20 +67,6 @@ def resolve_source(raw: str) -> tuple[str, str]:
|
|||
raise HTTPException(status_code=400, detail="That doesn't look like a valid video link.")
|
||||
|
||||
|
||||
def _reference_url(job: DownloadJob, asset: MediaAsset | None) -> str | None:
|
||||
"""The clean "downloaded from" URL for the Downloads page: the canonical page URL yt-dlp
|
||||
recorded, else one derived from source_kind+source_ref (covers queued/older rows before the
|
||||
worker fills it). Returns None for edit derivatives — a clip's source is the user's own earlier
|
||||
download, not a web page, so there's no meaningful external URL to show."""
|
||||
if job.job_kind == "edit":
|
||||
return None
|
||||
if asset is not None and asset.source_webpage_url:
|
||||
return asset.source_webpage_url
|
||||
if job.source_kind in ("youtube", "url"):
|
||||
return service.source_url(job.source_kind, job.source_ref)
|
||||
return None
|
||||
|
||||
|
||||
def _serialize(job: DownloadJob, asset: MediaAsset | None) -> dict:
|
||||
ready = asset is not None and asset.status == "ready" and bool(asset.rel_path)
|
||||
return {
|
||||
|
|
@ -95,7 +81,7 @@ def _serialize(job: DownloadJob, asset: MediaAsset | None) -> dict:
|
|||
"created_at": job.created_at.isoformat() if job.created_at else None,
|
||||
"source_kind": job.source_kind,
|
||||
"source_ref": job.source_ref,
|
||||
"source_url": _reference_url(job, asset),
|
||||
"source_url": service.reference_url(job, asset),
|
||||
# A locally-generated poster to fall back to when the source had no thumbnail.
|
||||
"poster_url": f"/api/downloads/{job.id}/poster.jpg"
|
||||
if (asset is not None and asset.poster_path)
|
||||
|
|
@ -139,6 +125,13 @@ def _assets_for(db: Session, jobs: list[DownloadJob]) -> dict[int, MediaAsset]:
|
|||
return {a.id: a for a in rows}
|
||||
|
||||
|
||||
def _serialize_job(db: Session, job: DownloadJob) -> dict:
|
||||
"""Resolve a job's cache asset (if any) and serialize the pair — the resolve-then-serialize step
|
||||
every single-job handler shares."""
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
|
||||
|
||||
def _own_job(db: Session, user: User, job_id: int) -> DownloadJob:
|
||||
job = db.get(DownloadJob, job_id)
|
||||
if job is None or job.user_id != user.id:
|
||||
|
|
@ -272,8 +265,7 @@ def enqueue_download(
|
|||
)
|
||||
except quota.QuotaExceeded as e:
|
||||
raise HTTPException(status_code=422, detail=_quota_message(e))
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
@router.post("/edit")
|
||||
|
|
@ -298,8 +290,7 @@ def enqueue_edit(
|
|||
raise HTTPException(status_code=422, detail=_quota_message(e))
|
||||
except service.EditError as e:
|
||||
raise HTTPException(status_code=400, detail=_edit_message(e))
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
def _edit_message(e: service.EditError) -> str:
|
||||
|
|
@ -429,8 +420,7 @@ def update_download_meta(
|
|||
cleaned = [u for u in (_clean_url(x) for x in raw) if u][:_MAX_EXTRA_LINKS]
|
||||
job.extra_links = cleaned or None
|
||||
db.commit()
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
def _set_status(db: Session, job: DownloadJob, status: str) -> None:
|
||||
|
|
@ -445,8 +435,7 @@ def pause_download(
|
|||
job = _own_job(db, user, job_id)
|
||||
if job.status in ("queued", "running"):
|
||||
_set_status(db, job, "paused") # a running worker aborts cooperatively via the hook
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
@router.post("/{job_id}/resume")
|
||||
|
|
@ -466,8 +455,7 @@ def resume_download(
|
|||
asset.status = "pending"
|
||||
asset.error = None
|
||||
_set_status(db, job, "queued")
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
@router.post("/{job_id}/cancel")
|
||||
|
|
@ -479,8 +467,7 @@ def cancel_download(
|
|||
_release_asset(db, job) # release while the job still counts as holding
|
||||
job.status = "canceled"
|
||||
db.commit()
|
||||
asset = db.get(MediaAsset, job.asset_id) if job.asset_id else None
|
||||
return _serialize(job, asset)
|
||||
return _serialize_job(db, job)
|
||||
|
||||
|
||||
@router.delete("/{job_id}")
|
||||
|
|
@ -514,11 +501,6 @@ def _release_asset(db: Session, job: DownloadJob) -> None:
|
|||
|
||||
# --- file download (range-aware, custom display name) --------------------------------------
|
||||
|
||||
def _clean_basename(name: str) -> str:
|
||||
# Emoji/symbol-free but keeps spaces + accents (a readable device filename).
|
||||
return storage.display_filename(name)
|
||||
|
||||
|
||||
def _accessible_job(db: Session, user: User, job_id: int) -> DownloadJob:
|
||||
job = db.get(DownloadJob, job_id)
|
||||
if job is None:
|
||||
|
|
@ -550,11 +532,9 @@ def download_file(
|
|||
if path is None:
|
||||
raise HTTPException(status_code=404, detail="File is no longer available.")
|
||||
|
||||
ext = asset.container or path.suffix.lstrip(".")
|
||||
base = _clean_basename(job.display_name or asset.title or asset.source_ref)
|
||||
if base.lower().endswith(f".{ext.lower()}"):
|
||||
base = base[: -(len(ext) + 1)]
|
||||
filename = f"{base}.{ext}"
|
||||
filename = storage.download_filename(
|
||||
job.display_name or asset.title or asset.source_ref, asset.container, path
|
||||
)
|
||||
|
||||
asset.last_access_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue