scheduling and jobs, new dataclasses and such better UDP handling
This commit is contained in:
79
app/utils/schedule_manager.py
Normal file
79
app/utils/schedule_manager.py
Normal 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
|
||||
Reference in New Issue
Block a user