maxSize = (int) Settings::settingValue('maxsizetoprocessnfo'); $this->minSize = (int) Settings::settingValue('minsizetoprocessnfo'); $this->maxRetries = (int) Settings::settingValue('maxnforetries') >= 0 ? -((int) Settings::settingValue('maxnforetries') + 1) : NfoService::NFO_UNPROC; $this->maxRetries = max($this->maxRetries, -8); // Initialize runners $this->backfillRunner = new BackfillRunner; $this->binariesRunner = new BinariesRunner; $this->releasesRunner = new ReleasesRunner; $this->postProcessRunner = new PostProcessRunner; } /** * Process backfill for all groups with backfill enabled. * * @param array $options */ public function backfill(array $options = []): void { $this->runWithTiming('backfill', fn () => $this->backfillRunner->backfill($options)); } /** * Process safe backfill (ordered by oldest first). */ public function safeBackfill(): void { $this->runWithTiming('safe_backfill', fn () => $this->backfillRunner->safeBackfill()); } /** * Download binaries (new headers) for all active groups. */ public function binaries(int $maxPerGroup = 0): void { $this->runWithTiming('binaries', fn () => $this->binariesRunner->binaries($maxPerGroup)); } /** * Process safe binaries (ordered by most recent activity). */ public function safeBinaries(): void { $this->runWithTiming('safe_binaries', fn () => $this->binariesRunner->safeBinaries()); } /** * Process releases for all groups. */ public function releases(): void { $this->runWithTiming('releases', function () { $this->releasesRunner->releases(); $this->processReleasesEndWork(); }); } /** * Update binaries and releases per group. */ public function updatePerGroup(): void { $this->runWithTiming('update_per_group', function () { $this->releasesRunner->updatePerGroup(); $this->processReleasesEndWork(); }); } /** * Fix release names using specified mode. * * @param string $mode 'standard' or 'predbft' */ public function fixRelNames(string $mode): void { $this->runWithTiming("fixRelNames_{$mode}", fn () => $this->releasesRunner->fixRelNames( $mode, (int) Settings::settingValue('fixnamesperrun'), (int) Settings::settingValue('fixnamethreads') )); } /** * Process additional post-processing (preview images, samples, etc). */ public function processAdditional(): void { $this->runWithTiming('postProcess_add', fn () => $this->postProcessRunner->processAdditional()); } /** * Process NFO files. */ public function processNfo(): void { $this->runWithTiming('postProcess_nfo', fn () => $this->postProcessRunner->processNfo()); } /** * Process movie metadata. */ public function processMovies(bool $renamedOnly = false): void { $this->runWithTiming('postProcess_mov', fn () => $this->postProcessRunner->processMovies($renamedOnly)); } /** * Process TV metadata. */ public function processTv(bool $renamedOnly = false): void { $this->runWithTiming('postProcess_tv', function () use ($renamedOnly) { if ($this->postProcessRunner->hasTvWork($renamedOnly)) { $this->postProcessRunner->processTv($renamedOnly); } else { $this->log('No TV work to do.'); } }); } /** * Process anime metadata. */ public function processAnime(): void { $this->runWithTiming('postProcess_ani', fn () => $this->postProcessRunner->processAnime()); } /** * Process book metadata. */ public function processBooks(): void { $this->runWithTiming('postProcess_boo', fn () => $this->postProcessRunner->processBooks()); } /** * Process music metadata. */ public function processMusic(): void { $this->runWithTiming('postProcess_mus', fn () => $this->postProcessRunner->processMusic()); } /** * Process console game metadata. */ public function processConsoles(): void { $this->runWithTiming('postProcess_con', fn () => $this->postProcessRunner->processConsoles()); } /** * Process PC game metadata. */ public function processGames(): void { $this->runWithTiming('postProcess_gam', fn () => $this->postProcessRunner->processGames()); } /** * Process books, music, console, and games metadata in parallel. */ public function processAmazon(): void { $this->runWithTiming('postProcess_ama', fn () => $this->postProcessRunner->processAmazon()); } /** * Generic work type processor for backwards compatibility. * * @deprecated Use specific methods instead (e.g., backfill(), binaries(), etc.) * * @param array $options */ public function processWorkType(string $type, array $options = []): void { match ($type) { 'backfill' => $this->backfill($options), 'binaries' => $this->binaries((int) ($options[0] ?? 0)), 'fixRelNames_standard' => $this->fixRelNames('standard'), 'fixRelNames_predbft' => $this->fixRelNames('predbft'), 'releases' => $this->releases(), 'postProcess_add' => $this->processAdditional(), 'postProcess_ani' => $this->processAnime(), 'postProcess_ama' => $this->processAmazon(), 'postProcess_boo' => $this->processBooks(), 'postProcess_con' => $this->processConsoles(), 'postProcess_gam' => $this->processGames(), 'postProcess_mov' => $this->processMovies($options[0] ?? false), 'postProcess_mus' => $this->processMusic(), 'postProcess_nfo' => $this->processNfo(), 'postProcess_tv' => $this->processTv($options[0] ?? false), 'safe_backfill' => $this->safeBackfill(), 'safe_binaries' => $this->safeBinaries(), 'update_per_group' => $this->updatePerGroup(), default => Log::warning("Unknown work type: {$type}"), }; } /** * Run a task with timing output. */ protected function runWithTiming(string $taskName, callable $task): void { $startTime = now()->timestamp; try { $task(); } catch (\Throwable $e) { Log::error("Task {$taskName} failed: {$e->getMessage()}", [ 'exception' => $e, ]); throw $e; } if (config('nntmux.echocli')) { cli()->header( "Multi-processing for {$taskName} finished in ".(now()->timestamp - $startTime). ' seconds at '.now()->toRfc2822String().'.'.PHP_EOL ); } } /** * Process end work for releases (DNR signalling). */ protected function processReleasesEndWork(): void { $count = $this->getReleaseWorkCount(); $command = $this->backfillRunner->buildDnrCommandPublic("releases {$count}_"); $this->executeCommand($command); } /** * Count groups with pending collections. */ protected function getReleaseWorkCount(): int { $groups = DB::select('SELECT id FROM usenet_groups WHERE (active = 1 OR backfill = 1)'); $count = 0; foreach ($groups as $group) { try { $query = DB::select( sprintf('SELECT id FROM collections WHERE groups_id = %d LIMIT 1', $group->id) ); if (! empty($query)) { $count++; } } catch (\PDOException $e) { if (config('app.debug')) { Log::debug($e->getMessage()); } } } return $count; } /** * Execute a shell command. */ protected function executeCommand(string $command): string { $process = Process::fromShellCommandline($command); $process->setTimeout(1800); $process->run(function ($type, $buffer) { if ($type === Process::ERR) { echo $buffer; } }); return $process->getOutput(); } /** * Log a message to console. */ protected function log(string $message): void { if (config('nntmux.echocli')) { echo $message.PHP_EOL; } } }