<?php namespace App\Console\Commands; use Carbon\Carbon; use Illuminate\Console\Command; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use App\Jobs\CatalogScan; use App\Traits\Type; class CatalogScanAll extends Command { use DispatchesJobs,Type; private int|bool $depth = true; protected const chunk_size = 5; /** * The name and signature of the console command. * * @var string */ protected $signature = 'catalog:scanall' .' {type : Photo | Video }' .' {--i|ignore : Ignore missing files}' .' {--s|scan : Force Rescan of all files }'; /** * The console command description. * * @var string */ protected $description = '(re)Scan Media'; /** * Execute the console command. * * @return int */ public function handle(): int { $started = Carbon::now(); $class = $this->getModelType($this->argument('type')); // Flag everything if we are forcing a rescan if ($this->option('scan')) $class::select('id')->update(['flag'=>TRUE]); Log::info('Scanning disk: '.Storage::disk('nas')->path('')); $c = 0; // Scan files in dir, and make sure file lives in DB, (touch it if it does), otherwise create it foreach (Storage::disk('nas')->directories($class::dir_prefix()) as $dir) { Log::info(sprintf(' - DIR: %s',$dir)); // Take x files at a time and check the DB foreach ($this->files($dir,$class::config,$class::dir_prefix())->chunk(self::chunk_size) as $chunk) { $list = $class::whereIn('filename',$chunk)->get(); // If there is a new file found it wont be in the DB if ($list->count() !== self::chunk_size) foreach ($chunk->diff($list->pluck('filename')) as $file) { Log::info(sprintf('Found new file [%s] - queueing scan',$file)); $o = new $class; $o->filename = $file; $o->save(); CatalogScan::dispatch($o) ->onQueue('scan'); $c++; } foreach ($list as $o) { if ($o->flag) { $o->scanned = FALSE; $o->flag = FALSE; $o->touch(); if ($this->option('scan')) { Log::info(sprintf('Forcing re-scan of [%s] - queued',$o->filename)); CatalogScan::dispatch($o) ->onQueue('scan'); } } else { $o->flag = FALSE; $o->touch(); } $c++; } } } Log::info('Checking for missing files'); // Find DB records before $started, check they exist (they shouldnt), and delete if not if (! $this->option('ignore')) foreach ($class::select(['id','filename'])->where('updated_at','<',$started)->cursor() as $o) Log::error(sprintf('It appears that file [%s] is missing (%d)',$o->filename,$o->id)); Log::info(sprintf('Processed [%s]',$c)); return self::SUCCESS; } /** * Recursively find files that we should catalog * * @param string $dir Directory to get files from * @param string $type Configuration key to refer to config()) * @param string $prefix Remove the prefix from the filename * @return Collection */ public function files(string $dir,string $type,string $prefix): Collection { $files = collect(Storage::disk('nas')->files($dir)) ->map(fn($item)=>preg_replace('#^'.$prefix.'#','',$item)) ->filter(function($item) use ($type) { return ((! ($x=strrpos($item,'.'))) || (! in_array(strtolower(substr($item,$x+1)),config($type.'.import.accepted')))) ? NULL : $item; }); if (! $this->depth) return $files; if (is_numeric($this->depth)) $this->depth--; foreach (Storage::disk('nas')->directories($dir) as $dir) $files = $files->merge($this->files($dir,$type,$prefix)); if (is_numeric($this->depth)) $this->depth++; return $files; } }