import * as plugins from './smarttime.plugins.js'; import { CronJob, type TJobFunction } from './smarttime.classes.cronjob.js'; export class CronManager { public executionTimeout: plugins.smartdelay.Timeout; public status: 'started' | 'stopped' = 'stopped'; public cronjobs = new plugins.lik.ObjectMap(); private cycleWakeDeferred: plugins.smartpromise.Deferred | null = null; constructor() {} /** * Resolves the current wake deferred, causing the sleeping cycle * to immediately recalculate instead of waiting for its timeout. */ private wakeCycle() { if (this.cycleWakeDeferred && this.cycleWakeDeferred.status === 'pending') { this.cycleWakeDeferred.resolve(); } } public addCronjob(cronIdentifierArg: string, cronFunctionArg: TJobFunction) { const newCronJob = new CronJob(this, cronIdentifierArg, cronFunctionArg); this.cronjobs.add(newCronJob); if (this.status === 'started') { newCronJob.start(); this.wakeCycle(); } return newCronJob; } public removeCronjob(cronjobArg: CronJob) { cronjobArg.stop(); this.cronjobs.remove(cronjobArg); if (this.status === 'started') { this.wakeCycle(); } } /** * starts the cronjob */ public start() { if (this.status !== 'started') { this.status = 'started'; for (const cronJob of this.cronjobs.getArray()) { cronJob.start(); } this.runCronCycle(); } } private async runCronCycle() { while (this.status === 'started') { // Create a fresh wake signal for this iteration this.cycleWakeDeferred = new plugins.smartpromise.Deferred(); // Check all cronjobs and find the soonest next execution let soonestMs = Infinity; for (const cronJob of this.cronjobs.getArray()) { cronJob.checkExecution(); const msToNext = cronJob.getTimeToNextExecution(); if (msToNext < soonestMs) { soonestMs = msToNext; } } // Sleep until the next job is due or until woken by a lifecycle event if (soonestMs < Infinity && soonestMs > 0) { this.executionTimeout = new plugins.smartdelay.Timeout(soonestMs); await Promise.race([ this.executionTimeout.promise, this.cycleWakeDeferred.promise, ]); // Cancel the timeout to avoid lingering timers this.executionTimeout.cancel(); } else if (soonestMs <= 0) { // Job is overdue, loop immediately to execute it continue; } else { // No jobs — wait indefinitely until woken by addCronjob or stop await this.cycleWakeDeferred.promise; } } this.cycleWakeDeferred = null; } /** * stops all cronjobs */ public stop() { if (this.status === 'started') { this.status = 'stopped'; if (this.executionTimeout) { this.executionTimeout.cancel(); } this.wakeCycle(); } for (const cron of this.cronjobs.getArray()) { cron.stop(); } } }