Files
newznab-tmux/app/Console/Commands/FetchSamples.php
T
2026-02-26 14:26:07 +01:00

155 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Release;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class FetchSamples extends Command
{
protected $signature = 'releases:fetch-samples
{--category= : Category id or comma-separated list of category ids (required)}
{--limit=0 : Max number of releases to process (0 = all)}
{--chunk=500 : Chunk size when iterating releases}
{--dry-run : Show how many and which GUIDs would be processed without running}
{--show-output : Display output from each releases:additional invocation}';
protected $description = 'Fetch/generate samples by running additional postprocessing (with reset) for releases in the supplied category / categories having jpgstatus = 0.';
public function handle(): int
{
$limit = (int) $this->option('limit');
$chunkSize = (int) $this->option('chunk');
$dryRun = (bool) $this->option('dry-run');
$showOutput = (bool) $this->option('show-output');
$categoryOpt = $this->option('category');
// Validate the required category option.
if ($categoryOpt === null || trim((string) $categoryOpt) === '') {
$this->info('Category option is empty. Provide --category with one or more numeric category IDs. Command will not run.');
return self::SUCCESS;
}
// Parse categories: allow comma or whitespace separated values.
$catIds = collect(preg_split('/[\s,]+/', trim((string) $categoryOpt), -1, PREG_SPLIT_NO_EMPTY))
->map(fn ($v) => trim($v))
->filter(fn ($v) => ctype_digit($v))
->map(fn ($v) => (int) $v)
->unique()
->values();
if ($catIds->isEmpty()) {
$this->info('Category option provided but no valid numeric IDs parsed. Command will not run.');
return self::SUCCESS;
}
if ($limit < 0) {
$this->error('Limit must be >= 0');
return self::FAILURE;
}
if ($chunkSize < 1) {
$this->error('Chunk size must be >= 1');
return self::FAILURE;
}
// Build base query now
$baseQuery = Release::query()
->whereIn('categories_id', $catIds->all())
->where('jpgstatus', 0)
->orderBy('id', 'desc');
$totalAll = $baseQuery->count();
if ($totalAll === 0) {
$this->info('No matching releases found (categories_id IN ['.implode(',', $catIds->all()).'] AND jpgstatus = 0).');
return self::SUCCESS;
}
$effectiveTotal = $limit > 0 ? min($limit, $totalAll) : $totalAll;
$this->info('Categories: ['.implode(',', $catIds->all()).']');
$this->info("Found {$totalAll} matching release(s). Processing {$effectiveTotal}.".($dryRun ? ' (dry-run)' : ''));
if ($dryRun) {
$previewQuery = clone $baseQuery;
if ($limit > 0) {
$previewQuery->limit($limit);
}
$previewGuids = $previewQuery->pluck('guid');
$this->line('Dry run: GUIDs to process (with --reset):');
foreach ($previewGuids as $g) {
$this->line($g);
}
$this->info('Dry run complete.');
return self::SUCCESS;
}
$processed = 0;
$failed = 0;
$remaining = $effectiveTotal;
$bar = $this->output->createProgressBar($effectiveTotal);
$bar->start();
$query = clone $baseQuery;
// Use chunkByIdDesc to process newest releases first
$query->chunkByIdDesc($chunkSize, function ($releases) use (&$processed, &$failed, &$remaining, $bar, $showOutput) {
foreach ($releases as $release) {
if ($remaining <= 0) {
return false; // stop chunking
}
$guid = $release->guid;
try {
// Call the existing single-release additional processing command with this GUID.
$exitCode = Artisan::call('releases:additional', [
'guid' => $guid, // pass GUID explicitly
'--reset' => true,
]);
$subOutput = trim(Artisan::output());
if ($exitCode === 0) {
$processed++;
if ($showOutput && $subOutput !== '') {
$this->getOutput()->writeln("\n<info>{$guid}</info> -> {$subOutput}");
}
} else {
$failed++;
$this->getOutput()->writeln("\n<error>Non-zero exit code ({$exitCode}) for GUID {$guid}</error>".($showOutput && $subOutput !== '' ? "\n Output: {$subOutput}" : ''));
}
} catch (\Throwable $e) {
$failed++;
$this->getOutput()->writeln("\n<error>Error processing GUID {$guid}: {$e->getMessage()}</error>");
}
$remaining--;
$bar->advance();
if ($remaining <= 0) {
break; // exit foreach to stop further work
}
}
if ($remaining <= 0) {
return false; // signal chunkById to stop
}
return true; // continue chunking
}, 'id');
$bar->finish();
$this->newLine();
$this->info("Processing complete. Success: {$processed}, Failed: {$failed}.");
return $failed === 0 ? self::SUCCESS : self::FAILURE;
}
}