"""Short-lived-session DB helpers + staging paths shared by the worker parent and the child pipeline.""" from pathlib import Path from sqlalchemy import text from app.config import settings from app.db import SessionLocal from app.models import DownloadJob, MediaAsset # --- small DB helpers (short-lived sessions; the download itself holds no transaction) ------- def _set_job(job_id: int, **fields) -> None: with SessionLocal() as db: job = db.get(DownloadJob, job_id) if job is None: return for k, v in fields.items(): setattr(job, k, v) db.commit() def _job_status(job_id: int) -> str | None: with SessionLocal() as db: return db.execute( text("SELECT status FROM download_jobs WHERE id = :id"), {"id": job_id} ).scalar() def _touch_job(job_id: int) -> None: """Bump a running job's `updated_at` without changing any value — a stall-watchdog heartbeat for phases that legitimately write no progress. Extraction writes nothing, and a multi-client-set fallback re-writes identical values (progress 0 / phase 'downloading'), so ORM `onupdate` would NOT fire and two slow extraction attempts could otherwise cross the stall timeout and be killed mid-extraction though they'd have succeeded.""" with SessionLocal() as db: db.execute( text("UPDATE download_jobs SET updated_at=now() WHERE id=:id AND status='running'"), {"id": job_id}, ) db.commit() def _requeue_if_running(job_id: int, phase: str) -> None: """Put the job back to 'queued' for a later retry, but ONLY while it is still 'running'. The route flips status to paused/cancelled to stop a job; a blind `status='queued'` here would clobber that and silently revive a job the user just cancelled. The conditional WHERE makes the requeue a no-op unless we still own a running job.""" with SessionLocal() as db: db.execute( text( "UPDATE download_jobs SET status='queued', phase=:phase, updated_at=now() " "WHERE id=:id AND status='running'" ), {"id": job_id, "phase": phase}, ) db.commit() def _reset_asset_pending(asset_id: int) -> None: with SessionLocal() as db: asset = db.get(MediaAsset, asset_id) if asset and asset.status == "downloading": asset.status = "pending" db.commit() def _staging_dir(asset_id: int) -> Path: return Path(settings.download_root) / ".staging" / f"asset-{asset_id}" def _edit_staging_dir(asset_id: int) -> Path: return Path(settings.download_root) / ".staging" / f"edit-{asset_id}"