scheduling and jobs, new dataclasses and such better UDP handling

This commit is contained in:
2025-10-17 16:49:30 -05:00
parent 9956667c8f
commit 41306801ae
13 changed files with 771 additions and 169 deletions

View File

@@ -0,0 +1,79 @@
# scheduler_manager.py
from __future__ import annotations
import logging
from dataclasses import asdict
from typing import Callable, List, Optional
from zoneinfo import ZoneInfo
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from utils.scan_config_loader import ScanConfigFile
logger = logging.getLogger(__name__)
class ScanScheduler:
"""
Owns an APScheduler and schedules one job per ScanConfigFile that has scan_options.cron set.
"""
def __init__(self, timezone: str = "America/Chicago") -> None:
self.tz = ZoneInfo(timezone)
self.scheduler = BackgroundScheduler(timezone=self.tz)
def start(self) -> None:
"""
Start the underlying scheduler thread.
"""
if not self.scheduler.running:
self.scheduler.start()
logger.info("APScheduler started (tz=%s).", self.tz)
def shutdown(self) -> None:
"""
Gracefully stop the scheduler.
"""
if self.scheduler.running:
self.scheduler.shutdown(wait=False)
logger.info("APScheduler stopped.")
def schedule_configs(
self,
configs: List[ScanConfigFile],
run_scan_fn: Callable[[ScanConfigFile], None],
replace_existing: bool = True,
) -> int:
"""
Create/replace jobs for all configs with a valid cron.
Returns number of scheduled jobs.
"""
count = 0
for cfg in configs:
cron = (cfg.scan_options.cron or "").strip() if cfg.scan_options else ""
if not cron:
logger.info("Skipping schedule (no cron): %s", cfg.name)
continue
job_id = f"scan::{cfg.name}"
trigger = CronTrigger.from_crontab(cron, timezone=self.tz)
self.scheduler.add_job(
func=run_scan_fn,
trigger=trigger,
id=job_id,
args=[cfg],
max_instances=1,
replace_existing=replace_existing,
misfire_grace_time=300,
coalesce=True,
)
logger.info("Scheduled '%s' with cron '%s' (next run: %s)",
cfg.name, cron, self._next_run_time(job_id))
count += 1
return count
def _next_run_time(self, job_id: str):
j = self.scheduler.get_job(job_id)
if j and hasattr(j, "next_run_time"):
return j.next_run_time.isoformat() if j.next_run_time else None
return None