"""Acquisition jobs: upload a game archive/DLL, hand it to the Ghidra worker, poll status. The upload is streamed to a shared volume ($AMS_UPLOAD_DIR) that the worker container also mounts; only the path travels through Redis, not the bytes.""" from __future__ import annotations import os import shutil import uuid from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile from sqlalchemy import select from sqlalchemy.orm import Session from .. import models, schemas from ..db import get_db router = APIRouter(prefix="/jobs", tags=["jobs"]) UPLOAD_DIR = os.environ.get("AMS_UPLOAD_DIR", "./uploads") def _save_upload(upload: UploadFile) -> tuple[str, str]: """Stream the upload to the shared dir under a unique name; return (path, original_name).""" os.makedirs(UPLOAD_DIR, exist_ok=True) original = os.path.basename(upload.filename or "upload.bin") dest = os.path.join(UPLOAD_DIR, "{0}_{1}".format(uuid.uuid4().hex[:8], original)) with open(dest, "wb") as fh: shutil.copyfileobj(upload.file, fh) return dest, original @router.post("", response_model=schemas.JobOut, status_code=202) def create_job( file: UploadFile = File(..., description="an ISO/ZIP archive or a loose engine DLL"), game: str | None = Form(None, description="link the resulting snapshot to this game"), db: Session = Depends(get_db), ) -> models.Job: path, original = _save_upload(file) job = models.Job(source_name=original, source_path=path, game_name=game, status="queued") db.add(job) db.commit() db.refresh(job) try: from ...worker.queue import enqueue_acquisition job.rq_id = enqueue_acquisition(path, game, job.id) db.commit() db.refresh(job) except Exception as exc: # Redis/RQ down or missing — surface, don't leave a phantom job job.status = "failed" job.error = "enqueue failed: {0}".format(exc) db.commit() raise HTTPException(503, job.error) return job @router.get("", response_model=list[schemas.JobOut]) def list_jobs(db: Session = Depends(get_db)) -> list[models.Job]: return list(db.scalars(select(models.Job).order_by(models.Job.id.desc()))) @router.get("/{job_id}", response_model=schemas.JobOut) def get_job(job_id: int, db: Session = Depends(get_db)) -> models.Job: job = db.get(models.Job, job_id) if job is None: raise HTTPException(404, "job not found") return job