enabled()) { $this->info('Virus scanning is switched off.'); return self::SUCCESS; } $waiting = $this->dispatchFor(File::query()->where('scan_status', ScanStatus::Pending)); // Allowed through while the scanner was unreachable. Now that it // may be back, they are asked again — a file found infected at // this point is quarantined like any other, and its quarantine // notice says it was available in the meantime. $missed = $this->dispatchFor( File::query() ->where('scan_status', ScanStatus::NotScanned) ->where('scan_note', NotScannedReason::ScannerUnavailable->value), rescan: true, ); $this->info("Re-queued {$waiting} waiting file(s) and {$missed} that were missed while the scanner was down."); if ($this->option('existing')) { // Paced, because this can be a whole library at once and the // scanner is also serving today's uploads. An hour's worth per // run, since that is how often this command runs. $limit = $config->existingScanRatePerMinute() * 60; $old = $this->dispatchFor( File::query() ->where('scan_status', ScanStatus::NotScanned) ->where('scan_note', NotScannedReason::BeforeScanning->value), $limit, rescan: true, ); $this->info("Queued {$old} file(s) that had never been scanned."); } return self::SUCCESS; } /** * Nothing here changes a file's state before the scanner has spoken. * * An earlier version marked each file pending first, which reads as * tidy and is wrong twice over: pending means "withheld", so a * backfill would have hidden an entire library from its clients for * as long as it ran, and every file would then have been announced to * its recipients a second time when it came back. The job knows which * state it expects instead — see its $rescan. * * @param Builder $query */ private function dispatchFor(Builder $query, ?int $limit = null, bool $rescan = false): int { if ($limit !== null) { $query->limit($limit); } $ids = $query->orderBy('id')->pluck('id'); foreach ($ids as $id) { ScanFileJob::dispatch((int) $id, $rescan); } return $ids->count(); } }