Files
netfelix-audio-fix/server/services/scheduler.ts
Felix Förtsch 874f04b7a5 wire scheduler into queue, add retry, dev-reset cleanup, biome 2.4 migrate
- execute: actually call isInScheduleWindow/waitForWindow/sleepBetweenJobs in runSequential (they were dead code); emit queue_status SSE events (running/paused/sleeping/idle) so the pipeline's existing QueueStatus listener lights up
- review: POST /:id/retry resets an errored plan to approved, wipes old done/error jobs, rebuilds command from current decisions, queues fresh job
- scan: dev-mode DELETE now also wipes jobs + subtitle_files (previously orphaned after every dev reset)
- biome: migrate config to 2.4 schema, autoformat 68 files (strings + indentation), relax opinionated a11y/hooks-deps/index-key rules that don't fit this codebase
- routeTree.gen.ts regenerated after /nodes removal
2026-04-13 07:41:19 +02:00

83 lines
2.8 KiB
TypeScript

import { getConfig, setConfig } from "../db";
export interface SchedulerState {
job_sleep_seconds: number;
schedule_enabled: boolean;
schedule_start: string; // "HH:MM"
schedule_end: string; // "HH:MM"
}
export function getSchedulerState(): SchedulerState {
return {
job_sleep_seconds: parseInt(getConfig("job_sleep_seconds") ?? "0", 10),
schedule_enabled: getConfig("schedule_enabled") === "1",
schedule_start: getConfig("schedule_start") ?? "01:00",
schedule_end: getConfig("schedule_end") ?? "07:00",
};
}
export function updateSchedulerState(updates: Partial<SchedulerState>): void {
if (updates.job_sleep_seconds != null) setConfig("job_sleep_seconds", String(updates.job_sleep_seconds));
if (updates.schedule_enabled != null) setConfig("schedule_enabled", updates.schedule_enabled ? "1" : "0");
if (updates.schedule_start != null) setConfig("schedule_start", updates.schedule_start);
if (updates.schedule_end != null) setConfig("schedule_end", updates.schedule_end);
}
/** Check if current time is within the schedule window. */
export function isInScheduleWindow(): boolean {
const state = getSchedulerState();
if (!state.schedule_enabled) return true; // no schedule = always allowed
const now = new Date();
const minutes = now.getHours() * 60 + now.getMinutes();
const start = parseTime(state.schedule_start);
const end = parseTime(state.schedule_end);
// Handle overnight windows (e.g., 23:00 → 07:00). End is inclusive so
// "07:00 → 07:00" spans a full day and the closing minute is covered.
if (start <= end) {
return minutes >= start && minutes <= end;
} else {
return minutes >= start || minutes <= end;
}
}
/** Returns milliseconds until the next schedule window opens. */
export function msUntilWindow(): number {
const state = getSchedulerState();
const now = new Date();
const minutes = now.getHours() * 60 + now.getMinutes();
const start = parseTime(state.schedule_start);
if (minutes < start) {
return (start - minutes) * 60_000;
} else {
// Next day
return (24 * 60 - minutes + start) * 60_000;
}
}
/** Returns the schedule_start time as "HH:MM" for display. */
export function nextWindowTime(): string {
return getSchedulerState().schedule_start;
}
function parseTime(hhmm: string): number {
const [h, m] = hhmm.split(":").map(Number);
return h * 60 + m;
}
/** Sleep for the configured duration between jobs. */
export function sleepBetweenJobs(): Promise<void> {
const seconds = getSchedulerState().job_sleep_seconds;
if (seconds <= 0) return Promise.resolve();
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
/** Wait until the schedule window opens. Resolves immediately if already in window. */
export function waitForWindow(): Promise<void> {
if (isInScheduleWindow()) return Promise.resolve();
const ms = msUntilWindow();
return new Promise((resolve) => setTimeout(resolve, ms));
}