"""The RQ task body. Runs inside the Ghidra-equipped worker container. It re-points SQLAlchemy at the shared DATABASE_URL (the worker is a separate process from the API), drives the acquisition pipeline, and walks the Job row through its status transitions so the API/UI can poll progress.""" from __future__ import annotations import os import traceback from ..acquire import acquire from ..api.db import configure, get_session, init_db from ..api.models import Job def _set(job_id: int, **fields): """Patch a Job row in its own short-lived session (worker may run far from the API).""" db = get_session() try: job = db.get(Job, job_id) if job is None: return None for k, v in fields.items(): setattr(job, k, v) db.commit() return job finally: db.close() def run_acquisition( job_id: int, source_path: str, game_name: str | None = None, database_url: str | None = None, ) -> dict: """Acquire `source_path` into the catalog, updating Job #`job_id` as it goes. Returns a small result dict (also stored by RQ). Errors are recorded on the Job row and re-raised so RQ marks the job failed.""" configure(database_url) init_db() _set(job_id, status="started", error=None) try: result = acquire(source_path, game_name, sink="db") except Exception as exc: # record then re-raise so RQ sees the failure too _set(job_id, status="failed", error="{0}: {1}".format(type(exc).__name__, exc)) traceback.print_exc() raise _set(job_id, status="finished", snapshot_id=result.imported_id, dll_name=os.path.basename(result.dll), error=None) return {"snapshot_id": result.imported_id, "engine": result.engine, "dll": result.dll}