\JKingWeb\Arsse\Service\Serial\Driver::class, 'subprocess' => \JKingWeb\Arsse\Service\Subprocess\Driver::class, ]; /** @var Service\Driver */ protected $drv; protected $loop = false; protected $reload = false; public function __construct() { $driver = Arsse::$conf->serviceDriver; $this->drv = new $driver(); } public function watch(bool $loop = true): \DateTimeInterface { $this->loop = $loop; $this->signalInit(); $t = new \DateTime(); do { $this->checkIn(); static::cleanupPre(); $list = Arsse::$db->feedListStale(); if ($list) { $this->drv->queue(...$list); unset($list); $this->drv->exec(); $this->drv->clean(); } static::cleanupPost(); $t->add(Arsse::$conf->serviceFrequency); // @codeCoverageIgnoreStart if ($this->loop) { do { sleep((int) max(0, $t->getTimestamp() - time())); if (function_exists("pcntl_signal_dispatch")) { pcntl_signal_dispatch(); if ($this->reload) { $this->reload(); fwrite(\STDERR, Arsse::$lang->msg("Service.Reload").\PHP_EOL); } } } while ($this->loop && $t->getTimestamp() > time()); } // @codeCoverageIgnoreEnd } while ($this->loop); return $t; } public function reload(): void { $this->reload = false; Arsse::$user = Arsse::$db = Arsse::$conf = Arsse::$lang = Arsse::$obj = $this->drv = null; Arsse::bootstrap(); $this->__construct(); } public function checkIn(): bool { return Arsse::$db->metaSet("service_last_checkin", time(), "datetime"); } public static function hasCheckedIn(): bool { $checkin = Arsse::$db->metaGet("service_last_checkin"); // if the service has never checked in, return false if (!$checkin) { return false; } // convert the check-in timestamp to a DateTime instance $checkin = Date::normalize($checkin, "sql"); // get the checking interval $int = Arsse::$conf->serviceFrequency; // subtract twice the checking interval from the current time to yield the earliest acceptable check-in time $limit = new \DateTime(); $limit->sub($int); $limit->sub($int); // return whether the check-in time is within the acceptable limit return $checkin >= $limit; } public static function cleanupPre(): bool { // mark unsubscribed feeds as orphaned and delete orphaned feeds that are beyond their retention period Arsse::$db->feedCleanup(); // do the same for icons Arsse::$db->iconCleanup(); // delete expired log-in sessions Arsse::$db->sessionCleanup(); return true; } public static function cleanupPost(): bool { // delete old articles, according to configured thresholds $deleted = Arsse::$db->articleCleanup(); // if any articles were deleted, perform database maintenance if ($deleted) { Arsse::$db->driverMaintenance(); } return true; } protected function signalInit(): void { if (function_exists("pcntl_async_signals") && function_exists("pcntl_signal")) { // receive asynchronous signals if supported pcntl_async_signals(true); foreach ([\SIGABRT, \SIGINT, \SIGTERM] as $sig) { pcntl_signal($sig, [$this, "sigTerm"]); } pcntl_signal(\SIGHUP, [$this, "sigHup"]); } } /** Changes the condition for the service loop upon receiving a termination signal * * @codeCoverageIgnore */ protected function sigTerm(int $signo): void { $this->loop = false; } /** Changes the condition for the service loop upon receiving a hangup signal * * @codeCoverageIgnore */ protected function sigHup(int $signo): void { $this->reload = true; } }