setInterval(300); $this->setTimeSensitivity(IJob::TIME_SENSITIVE); } protected function run($argument): void { $budget = PHP_SAPI === 'cli' ? 240.0 : 20.0; $deadline = microtime(true) + $budget; $this->startScheduled(); foreach ($this->runs->findRunning(null, BackupService::KIND_FULL) as $run) { while (microtime(true) < $deadline && $run->getStatus() === 'running') { $run = $this->backups->step($run, min($deadline, microtime(true) + $budget)); } } $this->failStaleRestores(); $this->weeklyPrune(); foreach ($this->targets->list() as $t) { $this->alerts->checkStale($t); } } /** A restore run that has not moved for 6 hours is considered dead (it would block cleanup forever). */ private function failStaleRestores(): void { foreach ($this->runs->findRunning(null, 'restore') as $run) { if (time() - $run->getUpdatedAt() > 6 * 3600) { $run->setStatus(\OCA\NgBackup\Db\Run::FAILED); $run->setError('No progress for 6 hours; marked as failed'); $run->setFinishedAt(time()); $this->runs->update($run); $this->alerts->runFailed($run, '#' . $run->getTargetId()); } } } /** Once a week (after Sunday's backup), apply the retention policy to non-append-only locations. */ private function weeklyPrune(): void { $lastWeek = $this->appConfig->getValueString(Application::APP_ID, 'last_prune_week', ''); if (date('N') !== '7' || $lastWeek === date('o-W') || $this->runs->findRunning(null, BackupService::KIND_FULL) !== []) { return; } foreach ($this->targets->list() as $t) { if ($t->getAppendOnly()) { continue; } try { $this->prune->apply($t, false, true); } catch (\Throwable $e) { $this->logger->warning('NG Backup: cleanup of ' . $t->getName() . ' failed: ' . $e->getMessage()); } } $this->appConfig->setValueString(Application::APP_ID, 'last_prune_week', date('o-W')); } /** Daily schedule: appconfig ng_backup/schedule_time "HH:MM" (server time); empty = no schedule. */ private function startScheduled(): void { $at = $this->appConfig->getValueString(Application::APP_ID, 'schedule_time', ''); if (!preg_match('/^([01]\d|2[0-3]):[0-5]\d$/', $at)) { return; } $todayAt = strtotime(date('Y-m-d') . ' ' . $at); if (time() < $todayAt) { return; } foreach ($this->targets->list() as $t) { $last = $this->runs->lastFinished($t->getId(), BackupService::KIND_FULL); if ($this->runs->findRunning($t->getId(), BackupService::KIND_FULL) !== [] || ($last !== null && $last->getStartedAt() >= $todayAt)) { continue; } try { $this->backups->start($t, 'scheduled'); } catch (\Throwable $e) { $this->logger->warning('NG Backup: scheduled backup to ' . $t->getName() . ' not started: ' . $e->getMessage()); } } } }