make auto-process inbox a continuous polling loop instead of one-shot
Build and Push Docker Image / build (push) Successful in 59s
Build and Push Docker Image / build (push) Successful in 59s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "netfelix-audio-fix",
|
||||
"version": "2026.04.21.12",
|
||||
"version": "2026.04.21.13",
|
||||
"scripts": {
|
||||
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
|
||||
"dev:client": "vite",
|
||||
|
||||
+65
-11
@@ -1101,9 +1101,8 @@ app.post("/approve-batch", async (c) => {
|
||||
let processInboxAbort: AbortController | null = null;
|
||||
|
||||
/**
|
||||
* Single entry point for launching processInbox. Manages the abort controller
|
||||
* so every caller (manual button, auto-process toggle, post-scan auto-process)
|
||||
* can be stopped via the /process-inbox/stop endpoint.
|
||||
* One-shot trigger for the manual "Process Inbox →" button. Manages the abort
|
||||
* controller so in-progress runs can be stopped via /process-inbox/stop.
|
||||
* Returns false if a run is already in progress.
|
||||
*/
|
||||
export function startProcessInbox(): boolean {
|
||||
@@ -1134,6 +1133,67 @@ export function stopProcessInbox(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Auto-process loop ─────────────────────────────────────────────────────
|
||||
// Continuous polling loop: while enabled, checks every few seconds for
|
||||
// unsorted inbox items and processes them. The manual button is a one-shot;
|
||||
// this is the "always on" counterpart.
|
||||
const AUTO_PROCESS_POLL_MS = 5_000;
|
||||
let autoProcessTimer: Timer | null = null;
|
||||
|
||||
function scheduleAutoProcessTick() {
|
||||
if (autoProcessTimer) return;
|
||||
if (getConfig("auto_processing") !== "1") return;
|
||||
|
||||
autoProcessTimer = setTimeout(autoProcessTick, AUTO_PROCESS_POLL_MS);
|
||||
}
|
||||
|
||||
async function autoProcessTick() {
|
||||
autoProcessTimer = null;
|
||||
if (getConfig("auto_processing") !== "1") return;
|
||||
|
||||
// Don't overlap with a manual run
|
||||
if (processInboxAbort) {
|
||||
scheduleAutoProcessTick();
|
||||
return;
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
const { n } = db
|
||||
.prepare("SELECT COUNT(*) as n FROM review_plans WHERE status = 'pending' AND is_noop = 0 AND sorted = 0")
|
||||
.get() as { n: number };
|
||||
|
||||
if (n > 0) {
|
||||
processInboxAbort = new AbortController();
|
||||
const { signal } = processInboxAbort;
|
||||
|
||||
try {
|
||||
const result = await processInbox(db, getAudioLanguages(), undefined, {
|
||||
onStart: emitInboxSortStart,
|
||||
onProgress: emitInboxSortProgress,
|
||||
signal,
|
||||
});
|
||||
emitInboxSorted(result);
|
||||
} catch {
|
||||
emitInboxSorted({ moved_to_queue: 0, moved_to_review: 0 });
|
||||
} finally {
|
||||
processInboxAbort = null;
|
||||
}
|
||||
}
|
||||
|
||||
scheduleAutoProcessTick();
|
||||
}
|
||||
|
||||
export function startAutoProcessLoop() {
|
||||
scheduleAutoProcessTick();
|
||||
}
|
||||
|
||||
export function stopAutoProcessLoop() {
|
||||
if (autoProcessTimer) {
|
||||
clearTimeout(autoProcessTimer);
|
||||
autoProcessTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/process-inbox", async (c) => {
|
||||
if (!startProcessInbox()) {
|
||||
return c.json({ ok: false, error: "processing already running" }, 409);
|
||||
@@ -1534,10 +1594,7 @@ app.post("/:id/rescan", async (c) => {
|
||||
// Delete pending jobs
|
||||
db.prepare("DELETE FROM jobs WHERE item_id = ? AND status = 'pending'").run(id);
|
||||
|
||||
// Auto-process if enabled (processInbox handles language resolution + reanalysis)
|
||||
if (getConfig("auto_processing") === "1") {
|
||||
await processInbox(db, getAudioLanguages());
|
||||
}
|
||||
// Auto-process loop (if enabled) picks up the reset item automatically.
|
||||
|
||||
emitPipelineChanged();
|
||||
return c.json({ ok: true, inInbox: true });
|
||||
@@ -1571,10 +1628,7 @@ app.post("/rescan-series", async (c) => {
|
||||
db.prepare("DELETE FROM jobs WHERE item_id = ? AND status = 'pending'").run(item.id);
|
||||
}
|
||||
|
||||
// Auto-process if enabled
|
||||
if (getConfig("auto_processing") === "1") {
|
||||
await processInbox(db, getAudioLanguages());
|
||||
}
|
||||
// Auto-process loop (if enabled) picks up reset items automatically.
|
||||
|
||||
emitPipelineChanged();
|
||||
return c.json({ ok: true, count: items.length });
|
||||
|
||||
+1
-5
@@ -233,11 +233,7 @@ async function runScan(limit: number | null = null): Promise<void> {
|
||||
setConfig("scan_running", "0");
|
||||
log(`Scan complete: ${processed} scanned, ${errors} errors`);
|
||||
emitSse("complete", { scanned: processed, total, errors });
|
||||
|
||||
if (getConfig("auto_processing") === "1") {
|
||||
const { startProcessInbox } = await import("./review");
|
||||
startProcessInbox();
|
||||
}
|
||||
// Auto-process loop (if enabled) picks up new inbox items automatically.
|
||||
}
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -79,9 +79,9 @@ app.post("/audio-languages", async (c) => {
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
// Toggle the auto-processing flag. When flipped on, trigger a one-shot
|
||||
// sort-inbox pass so existing Inbox items drain immediately without waiting
|
||||
// for the next scan.
|
||||
// Toggle the auto-processing flag. When flipped on, start a continuous
|
||||
// polling loop that monitors the inbox and processes items as they arrive.
|
||||
// When flipped off, stop the loop and abort any in-progress auto-run.
|
||||
app.post("/auto-processing", async (c) => {
|
||||
const body = await c.req.json<{ enabled?: unknown }>().catch(() => ({ enabled: null }));
|
||||
if (typeof body.enabled !== "boolean") {
|
||||
@@ -90,10 +90,11 @@ app.post("/auto-processing", async (c) => {
|
||||
setConfig("auto_processing", body.enabled ? "1" : "0");
|
||||
|
||||
if (body.enabled) {
|
||||
const { startProcessInbox } = await import("./review");
|
||||
startProcessInbox();
|
||||
const { startAutoProcessLoop } = await import("./review");
|
||||
startAutoProcessLoop();
|
||||
} else {
|
||||
const { stopProcessInbox } = await import("./review");
|
||||
const { stopAutoProcessLoop, stopProcessInbox } = await import("./review");
|
||||
stopAutoProcessLoop();
|
||||
stopProcessInbox();
|
||||
}
|
||||
return c.json({ ok: true, enabled: body.enabled });
|
||||
|
||||
+6
-1
@@ -7,7 +7,7 @@ import pathsRoutes from "./api/paths";
|
||||
import reviewRoutes from "./api/review";
|
||||
import scanRoutes from "./api/scan";
|
||||
import settingsRoutes from "./api/settings";
|
||||
import { getDb } from "./db/index";
|
||||
import { getConfig, getDb } from "./db/index";
|
||||
import { log } from "./lib/log";
|
||||
|
||||
const app = new Hono();
|
||||
@@ -66,6 +66,11 @@ log(`netfelix-audio-fix v${pkg.version} starting on http://localhost:${port}`);
|
||||
|
||||
getDb();
|
||||
|
||||
// Resume auto-process loop if it was enabled before the server restarted
|
||||
if (getConfig("auto_processing") === "1") {
|
||||
import("./api/review").then(({ startAutoProcessLoop }) => startAutoProcessLoop());
|
||||
}
|
||||
|
||||
export default {
|
||||
port,
|
||||
fetch: app.fetch,
|
||||
|
||||
Reference in New Issue
Block a user