- 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
83 lines
2.8 KiB
TypeScript
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));
|
|
}
|