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) ); $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, ); $this->info("Queued {$old} file(s) that had never been scanned."); } return self::SUCCESS; } /** * @param Builder $query */ private function dispatchFor(Builder $query, ?int $limit = null): int { if ($limit !== null) { $query->limit($limit); } $ids = $query->orderBy('id')->pluck('id'); foreach ($ids as $id) { // Back to pending first: the job only acts on a pending file, // which is what stops two runs of this command from scanning // the same file twice. File::query()->whereKey($id)->update(['scan_status' => ScanStatus::Pending->value, 'scan_note' => null]); ScanFileJob::dispatch((int) $id); } return $ids->count(); } }