"""Canonical download-job status values — the ONE source of truth for the lifecycle literals. A job's status is written from several places: the worker (claim → running, terminal done/error, requeue), the routes (pause/cancel/redownload/delete), the service (enqueue), and the GC. Keeping the literals here is a data-safety measure, not tidiness. A British/American drift once silently broke cancellation: the supervisor compared the live status against ``"cancelled"`` while the cancel route wrote ``"canceled"`` (models.py), so the supervisor's "a route stopped it → kill the child, free the asset" branch was DEAD. A canceled job's child then ran to completion and wrote ``"done"`` unconditionally over the route's ``"canceled"``; because ``done`` counts as a holding state but ``canceled`` does not (see ``service.release_asset``), a later delete released the asset a SECOND time, dropping ``ref_count`` below the live holder count and deleting a file still in use. Import these names instead of typing the strings. """ from enum import StrEnum class JobStatus(StrEnum): QUEUED = "queued" RUNNING = "running" PAUSED = "paused" DONE = "done" ERROR = "error" CANCELED = "canceled" # A job "holds" its media asset (the +1 on ``ref_count`` from enqueue) while in any of these states; # ``release_asset`` on a status OUTSIDE this set is a no-op. ``CANCELED`` is deliberately excluded: the # cancel route releases once while the job is still holding, then sets ``CANCELED``, so a later delete # does not double-release. This invariant only holds while a finishing child cannot flip ``CANCELED`` # back into the set — hence the worker's terminal writes are conditional on the row still being RUNNING. HOLDING = frozenset( {JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.PAUSED, JobStatus.DONE, JobStatus.ERROR} ) class JobKind(StrEnum): """What a job produces, which decides the worker path that runs it and what the library may do with the result (only a DOWNLOAD has a web source to fetch again).""" DOWNLOAD = "download" # yt-dlp fetches a source into a shared, deduplicated asset EDIT = "edit" # a per-user trim/crop of a file already in the library UPLOAD = "upload" # a file the user sent; the worker only converts it CLIP = ( "clip" # a per-user range of a YouTube video or a Plex item, cut straight from the source ) # A job kind with no web source to fetch again: Re-download refuses these. NOT_REFETCHABLE = frozenset({JobKind.EDIT, JobKind.UPLOAD, JobKind.CLIP}) # States a route sets to STOP a running job. The worker's supervisor must step in (kill the child, free # the asset, leave the route's status intact) when it observes one of these on a job it is running. STOPPED_BY_ROUTE = frozenset({JobStatus.PAUSED, JobStatus.CANCELED}) # A job that will not move again on its own: the queue lists it only briefly after it got here. FINISHED = frozenset({JobStatus.DONE, JobStatus.CANCELED})