mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
322 lines
9.3 KiB
PHP
322 lines
9.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Settings;
|
|
use App\Services\Runners\BackfillRunner;
|
|
use App\Services\Runners\BinariesRunner;
|
|
use App\Services\Runners\PostProcessRunner;
|
|
use App\Services\Runners\ReleasesRunner;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Symfony\Component\Process\Process;
|
|
|
|
/**
|
|
* Service for multiprocessing various tasks.
|
|
*
|
|
* This service orchestrates parallel processing of usenet indexing tasks
|
|
* by delegating to specialized runner classes.
|
|
*/
|
|
class ForkingService
|
|
{
|
|
protected BackfillRunner $backfillRunner;
|
|
|
|
protected BinariesRunner $binariesRunner;
|
|
|
|
protected ReleasesRunner $releasesRunner;
|
|
|
|
protected PostProcessRunner $postProcessRunner;
|
|
|
|
protected int $maxRetries;
|
|
|
|
public function __construct()
|
|
{
|
|
$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<string, mixed> $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<string, mixed> $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((int) config('nntmux.multiprocessing_max_child_time', 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;
|
|
}
|
|
}
|
|
}
|