All checks were successful
Build and Push Docker Image / build (push) Successful in 58s
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
import { getConfig, setConfig } from "../db";
|
|
|
|
export interface ScheduleWindow {
|
|
enabled: boolean;
|
|
start: string; // "HH:MM" — 24h
|
|
end: string; // "HH:MM" — 24h
|
|
}
|
|
|
|
export interface ScheduleConfig {
|
|
job_sleep_seconds: number;
|
|
scan: ScheduleWindow;
|
|
process: ScheduleWindow;
|
|
}
|
|
|
|
type WindowKind = "scan" | "process";
|
|
|
|
const DEFAULTS: Record<WindowKind, ScheduleWindow> = {
|
|
scan: { enabled: false, start: "01:00", end: "07:00" },
|
|
process: { enabled: false, start: "01:00", end: "07:00" },
|
|
};
|
|
|
|
function readWindow(kind: WindowKind): ScheduleWindow {
|
|
return {
|
|
enabled: getConfig(`${kind}_schedule_enabled`) === "1",
|
|
start: getConfig(`${kind}_schedule_start`) ?? DEFAULTS[kind].start,
|
|
end: getConfig(`${kind}_schedule_end`) ?? DEFAULTS[kind].end,
|
|
};
|
|
}
|
|
|
|
function writeWindow(kind: WindowKind, w: Partial<ScheduleWindow>): void {
|
|
if (w.enabled != null) setConfig(`${kind}_schedule_enabled`, w.enabled ? "1" : "0");
|
|
if (w.start != null) setConfig(`${kind}_schedule_start`, w.start);
|
|
if (w.end != null) setConfig(`${kind}_schedule_end`, w.end);
|
|
}
|
|
|
|
export function getScheduleConfig(): ScheduleConfig {
|
|
return {
|
|
job_sleep_seconds: Number.parseInt(getConfig("job_sleep_seconds") ?? "0", 10),
|
|
scan: readWindow("scan"),
|
|
process: readWindow("process"),
|
|
};
|
|
}
|
|
|
|
const HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
|
|
function validateWindow(kind: WindowKind, w: Partial<ScheduleWindow>): void {
|
|
if (w.start != null && !HHMM.test(w.start)) {
|
|
throw new Error(`${kind}.start must be HH:MM 24h, got ${JSON.stringify(w.start)}`);
|
|
}
|
|
if (w.end != null && !HHMM.test(w.end)) {
|
|
throw new Error(`${kind}.end must be HH:MM 24h, got ${JSON.stringify(w.end)}`);
|
|
}
|
|
}
|
|
|
|
export function updateScheduleConfig(updates: Partial<ScheduleConfig>): void {
|
|
if (updates.job_sleep_seconds != null) {
|
|
const n = updates.job_sleep_seconds;
|
|
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0 || n > 86_400) {
|
|
throw new Error(`job_sleep_seconds must be an integer in [0, 86400], got ${JSON.stringify(n)}`);
|
|
}
|
|
setConfig("job_sleep_seconds", String(n));
|
|
}
|
|
if (updates.scan) {
|
|
validateWindow("scan", updates.scan);
|
|
writeWindow("scan", updates.scan);
|
|
}
|
|
if (updates.process) {
|
|
validateWindow("process", updates.process);
|
|
writeWindow("process", updates.process);
|
|
}
|
|
}
|
|
|
|
function parseTime(hhmm: string): number {
|
|
const [h, m] = hhmm.split(":").map(Number);
|
|
return h * 60 + m;
|
|
}
|
|
|
|
function isInWindow(w: ScheduleWindow): boolean {
|
|
if (!w.enabled) return true;
|
|
const now = new Date();
|
|
const minutes = now.getHours() * 60 + now.getMinutes();
|
|
const start = parseTime(w.start);
|
|
const end = parseTime(w.end);
|
|
// Overnight windows (e.g. 23:00 → 07:00) wrap midnight.
|
|
if (start <= end) return minutes >= start && minutes <= end;
|
|
return minutes >= start || minutes <= end;
|
|
}
|
|
|
|
function msUntilWindow(w: ScheduleWindow): number {
|
|
const now = new Date();
|
|
const minutes = now.getHours() * 60 + now.getMinutes();
|
|
const start = parseTime(w.start);
|
|
if (minutes < start) return (start - minutes) * 60_000;
|
|
return (24 * 60 - minutes + start) * 60_000;
|
|
}
|
|
|
|
function waitForWindow(w: ScheduleWindow): Promise<void> {
|
|
if (isInWindow(w)) return Promise.resolve();
|
|
return new Promise((resolve) => setTimeout(resolve, msUntilWindow(w)));
|
|
}
|
|
|
|
// ─── Scan window ─────────────────────────────────────────────────────────────
|
|
export const isInScanWindow = () => isInWindow(readWindow("scan"));
|
|
export const msUntilScanWindow = () => msUntilWindow(readWindow("scan"));
|
|
export const nextScanWindowTime = () => readWindow("scan").start;
|
|
export const waitForScanWindow = () => waitForWindow(readWindow("scan"));
|
|
|
|
// ─── Process window ──────────────────────────────────────────────────────────
|
|
export const isInProcessWindow = () => isInWindow(readWindow("process"));
|
|
export const msUntilProcessWindow = () => msUntilWindow(readWindow("process"));
|
|
export const nextProcessWindowTime = () => readWindow("process").start;
|
|
export const waitForProcessWindow = () => waitForWindow(readWindow("process"));
|
|
|
|
/** Sleep for the configured duration between jobs. */
|
|
export function sleepBetweenJobs(): Promise<void> {
|
|
const seconds = Number.parseInt(getConfig("job_sleep_seconds") ?? "0", 10);
|
|
if (seconds <= 0) return Promise.resolve();
|
|
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
|
}
|