diff options
Diffstat (limited to 'library/Reporting/Scheduler.php')
-rw-r--r-- | library/Reporting/Scheduler.php | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/library/Reporting/Scheduler.php b/library/Reporting/Scheduler.php new file mode 100644 index 0000000..1b8d9f6 --- /dev/null +++ b/library/Reporting/Scheduler.php @@ -0,0 +1,176 @@ +<?php +// Icinga Reporting | (c) 2018 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Reporting; + +use Cron\CronExpression; +use ipl\Sql\Connection; +use ipl\Sql\Select; +use React\EventLoop\Factory as Loop; + +function datetime_get_time_of_day(\DateTime $dateTime) +{ + $midnight = clone $dateTime; + $midnight->modify('midnight'); + + $diff = $midnight->diff($dateTime); + + return $diff->h * 60 * 60 + $diff->i * 60 + $diff->s; +} + +class Scheduler +{ + protected $db; + + protected $loop; + + /** @var array */ + protected $schedules = []; + + /** @var array */ + protected $timers = []; + + public function __construct(Connection $db) + { + $this->db = $db; + $this->loop = Loop::create(); + } + + public function run() + { + $updateTimers = function () use (&$updateTimers) { + $this->updateTimers(); + + $this->loop->addTimer(60, $updateTimers); + }; + + $this->loop->futureTick($updateTimers); + + $this->loop->run(); + } + + protected function fetchSchedules() + { + $schedules = []; + + $select = (new Select()) + ->from('schedule') + ->columns('*'); + + foreach ($this->db->select($select) as $row) { + $schedule = (new Schedule()) + ->setId((int) $row->id) + ->setReportId((int) $row->report_id) + ->setAction($row->action) + ->setConfig(\json_decode($row->config, true)) + ->setStart((new \DateTime())->setTimestamp((int) $row->start / 1000)) + ->setFrequency($row->frequency); + + $schedules[$schedule->getChecksum()] = $schedule; + } + + return $schedules; + } + + protected function updateTimers() + { + $schedules = $this->fetchSchedules(); + + $remove = \array_diff_key($this->schedules, $schedules); + + foreach ($remove as $schedule) { + printf("Removing job %s.\n", "Schedule {$schedule->getId()}"); + + $checksum = $schedule->getChecksum(); + + if (isset($this->timers[$checksum])) { + $this->loop->cancelTimer($this->timers[$checksum]); + unset($this->timers[$checksum]); + } else { + printf("Can't find timer for job %s.\n", $checksum); + } + } + + $add = \array_diff_key($schedules, $this->schedules); + + foreach ($add as $schedule) { + $this->add($schedule); + } + + $this->schedules = $schedules; + } + + + protected function add(Schedule $schedule) + { + $name = "Schedule {$schedule->getId()}"; + $frequency = $schedule->getFrequency(); + $start = clone $schedule->getStart(); + $callback = function () use ($schedule) { + $actionClass = $schedule->getAction(); + /** @var ActionHook $action */ + $action = new $actionClass; + + $action->execute( + Report::fromDb($schedule->getReportId()), + $schedule->getConfig() + ); + }; + + switch ($frequency) { + case 'minutely': + $modify = '+1 minute'; + break; + case 'hourly': + $modify = '+1 hour'; + break; + case 'daily': + $modify = '+1 day'; + break; + case 'weekly': + $modify = '+1 week'; + break; + case 'monthly': + $modify = '+1 month'; + break; + default: + throw new \InvalidArgumentException('Invalid frequency.'); + } + + $now = new \DateTime(); + + if ($start < $now) { +// printf("Scheduling job %s to run immediately.\n", $name); +// $this->loop->futureTick($callback); + + while ($start < $now) { + $start->modify($modify); + } + } + + $next = clone $start; + $next->modify($modify); + $interval = $next->getTimestamp() - $start->getTimestamp(); + + $current = $start->getTimestamp() - $now->getTimestamp(); + + printf("Scheduling job %s to run at %s.\n", $name, $start->format('Y-m-d H:i:s')); + + $loop = function () use (&$loop, $name, $callback, $interval, $schedule) { + $callback(); + + $nextRun = (new \DateTime()) + ->add(new \DateInterval("PT{$interval}S")); + + printf("Scheduling job %s to run at %s.\n", $name, $nextRun->format('Y-m-d H:i:s')); + + $timer = $this->loop->addTimer($interval, $loop); + + $this->timers[$schedule->getChecksum()] = $timer; + }; + + $timer = $this->loop->addTimer($current, $loop); + + $this->timers[$schedule->getChecksum()] = $timer; + } +} |