diff --git a/server/api/execute.ts b/server/api/execute.ts index 3146e7c..a622bd5 100644 --- a/server/api/execute.ts +++ b/server/api/execute.ts @@ -3,6 +3,7 @@ import { Hono } from "hono"; import { stream } from "hono/streaming"; import { getDb } from "../db/index"; import { log, error as logError, warn } from "../lib/log"; +import { parseId } from "../lib/validate"; import { predictExtractedFiles } from "../services/ffmpeg"; import { getScheduleConfig, @@ -31,12 +32,18 @@ function emitQueueStatus( for (const l of jobListeners) l(line); } -async function runSequential(jobs: Job[]): Promise { +async function runSequential(initial: Job[]): Promise { if (queueRunning) return; queueRunning = true; try { let first = true; - for (const job of jobs) { + const queue: Job[] = [...initial]; + const seen = new Set(queue.map((j) => j.id)); + + while (queue.length > 0) { + // biome-ignore lint/style/noNonNullAssertion: length checked above + const job = queue.shift()!; + // Pause outside the processing window if (!isInProcessWindow()) { emitQueueStatus("paused", { @@ -70,6 +77,19 @@ async function runSequential(jobs: Job[]): Promise { } catch (err) { logError(`Job ${job.id} failed:`, err); } + + // When the local queue drains, re-check the DB for jobs that were + // approved mid-run. Without this they'd sit pending until the user + // manually clicks "Run all" again. + if (queue.length === 0) { + const more = db.prepare("SELECT * FROM jobs WHERE status = 'pending' ORDER BY created_at").all() as Job[]; + for (const m of more) { + if (!seen.has(m.id)) { + queue.push(m); + seen.add(m.id); + } + } + } } } finally { queueRunning = false; @@ -137,14 +157,6 @@ function loadJobRow(jobId: number) { return { job: row as unknown as Job, item }; } -// ─── Param helpers ──────────────────────────────────────────────────────────── - -function parseId(raw: string | undefined): number | null { - if (!raw) return null; - const n = Number.parseInt(raw, 10); - return Number.isFinite(n) && n > 0 ? n : null; -} - // ─── Start all pending ──────────────────────────────────────────────────────── app.post("/start", (c) => {