Files
newznab-tmux/Blacklight/libraries/Forking.php
T
2025-08-15 17:13:43 +02:00

349 lines
10 KiB
PHP
Executable File

<?php
namespace Blacklight\libraries;
use App\Models\Settings;
use Blacklight\ColorCLI;
use Blacklight\libraries\Runners\BackfillRunner;
use Blacklight\libraries\Runners\BinariesRunner;
use Blacklight\libraries\Runners\PostProcessRunner;
use Blacklight\libraries\Runners\ReleasesRunner;
use Blacklight\Nfo;
use Blacklight\processing\PostProcess;
use Illuminate\Support\Facades\DB;
use Spatie\Async\Pool;
use Symfony\Component\Process\Process;
/**
* Class Forking.
*
* This forks various scripts.
*
* For example, you get all the ID's of the active groups in the groups table, you then iterate over them and spawn
* processes of misc/update_binaries.php passing the group ID's.
*/
class Forking
{
public ColorCLI $colorCli;
/**
* @var int The type of output
*/
protected int $outputType;
/**
* Path to do not run folder.
*/
private string $dnr_path;
/**
* Work to work on.
*/
private array $work = [];
/**
* How much work do we have to do?
*/
public int $_workCount = 0;
/**
* The type of work we want to work on.
*/
private string $workType = '';
/**
* List of passed in options for the current work type.
*/
private array $workTypeOptions = [];
/**
* Max amount of child processes to do work at a time.
*/
private int $maxProcesses = 1;
/**
* Group used for safe backfill.
*/
private string $safeBackfillGroup = '';
protected int $maxSize;
protected int $minSize;
protected int $maxRetries;
protected int $dummy;
private bool $processAdditional = false; // Should we process additional?
private bool $processNFO = false; // Should we process NFOs?
private bool $processMovies = false; // Should we process Movies?
private bool $processTV = false; // Should we process TV?
/** Runners **/
private BackfillRunner $backfillRunner;
private BinariesRunner $binariesRunner;
private ReleasesRunner $releasesRunner;
private PostProcessRunner $postProcessRunner;
/**
* Setup required parent / self vars.
*
* @throws \Exception
*/
public function __construct()
{
$this->colorCli = new ColorCLI;
$this->dnr_path = PHP_BINARY.' misc/update/multiprocessing/.do_not_run/switch.php "php ';
$this->maxSize = (int) Settings::settingValue('maxsizetoprocessnfo');
$this->minSize = (int) Settings::settingValue('minsizetoprocessnfo');
$this->maxRetries = (int) Settings::settingValue('maxnforetries') >= 0 ? -((int) Settings::settingValue('maxnforetries') + 1) : Nfo::NFO_UNPROC;
$this->maxRetries = max($this->maxRetries, -8);
// init runners
$this->backfillRunner = new BackfillRunner($this->colorCli);
$this->binariesRunner = new BinariesRunner($this->colorCli);
$this->releasesRunner = new ReleasesRunner($this->colorCli);
$this->postProcessRunner = new PostProcessRunner($this->colorCli);
}
/**
* Setup the class to work on a type of work, then process the work.
* Valid work types:.
*
* @param string $type The type of multiProcessing to do : backfill, binaries, releases, postprocess
* @param array $options Array containing arguments for the type of work.
*
* @throws \Exception
*/
public function processWorkType(string $type, array $options = []): void
{
// Set/reset some variables.
$startTime = now()->timestamp;
$this->workType = $type;
$this->workTypeOptions = $options;
$this->processAdditional = $this->processNFO = $this->processTV = $this->processMovies = $this->ppRenamedOnly = false;
$this->work = [];
// Get work to fork via runners
$this->getWork();
// Process extra work that should not be forked and done after.
$this->processEndWork();
if (config('nntmux.echocli')) {
$this->colorCli->header(
'Multi-processing for '.$this->workType.' finished in '.(now()->timestamp - $startTime).
' seconds at '.now()->toRfc2822String().'.'.PHP_EOL
);
}
}
/**
* Only post process renamed movie / tv releases?
*/
private bool $ppRenamedOnly;
/**
* Helper to ensure concurrency is at least 1 and with a common timeout.
*/
private function createPool(int $concurrency): Pool
{
$concurrency = max(1, $concurrency);
return Pool::create()
->concurrency($concurrency)
->timeout(config('nntmux.multiprocessing_max_child_time'));
}
/**
* Helper to build a DNR switch command.
*/
private function buildDnrCommand(string $args): string
{
return $this->dnr_path.$args.'"';
}
/**
* Get work for our workers to work on, set the max child processes here.
*
* @throws \Exception
*/
private function getWork(): void
{
$this->maxProcesses = 0;
switch ($this->workType) {
case 'backfill':
$this->backfillRunner->backfill($this->workTypeOptions);
break;
case 'binaries':
$maxPerGroup = (int) ($this->workTypeOptions[0] ?? 0);
$this->binariesRunner->binaries($maxPerGroup);
break;
case 'fixRelNames_standard':
$this->releasesRunner->fixRelNames('standard', (int) Settings::settingValue('fixnamesperrun'), (int) Settings::settingValue('fixnamethreads'));
break;
case 'fixRelNames_predbft':
$this->releasesRunner->fixRelNames('predbft', (int) Settings::settingValue('fixnamesperrun'), (int) Settings::settingValue('fixnamethreads'));
break;
case 'releases':
$this->releasesRunner->releases();
break;
case 'postProcess_ama':
$this->processSingle();
break;
case 'postProcess_add':
$this->postProcessRunner->processAdditional();
break;
case 'postProcess_mov':
$renamedOnly = (isset($this->workTypeOptions[0]) && $this->workTypeOptions[0] === true);
$this->postProcessRunner->processMovies($renamedOnly);
break;
case 'postProcess_nfo':
$this->postProcessRunner->processNfo();
break;
case 'postProcess_tv':
$renamedOnly = (isset($this->workTypeOptions[0]) && $this->workTypeOptions[0] === true);
if ($this->postProcessRunner->hasTvWork($renamedOnly)) {
$this->postProcessRunner->processTv($renamedOnly);
} else {
$this->logger('No TV work to do.');
}
break;
case 'safe_backfill':
$this->backfillRunner->safeBackfill();
break;
case 'safe_binaries':
$this->binariesRunner->safeBinaries();
break;
case 'update_per_group':
$this->releasesRunner->updatePerGroup();
break;
}
}
/**
* Process any work that does not need to be forked, but needs to run at the end.
*/
private function processEndWork(): void
{
switch ($this->workType) {
case 'update_per_group':
case 'releases':
$count = $this->getReleaseWorkCount();
$this->_executeCommand($this->buildDnrCommand('releases '.$count.'_'));
break;
}
}
/**
* Compute count of groups which currently have collections pending (used for DNR signalling).
*/
private function getReleaseWorkCount(): int
{
$groups = DB::select('SELECT id FROM usenet_groups WHERE (active = 1 OR backfill = 1)');
$count = 0;
foreach ($groups as $g) {
try {
$q = DB::select(sprintf('SELECT id FROM collections WHERE groups_id = %d LIMIT 1', $g->id));
if (! empty($q)) {
$count++;
}
} catch (\PDOException $e) {
if (config('app.debug') === true) {
\Log::debug($e->getMessage());
}
}
}
return $count;
}
// The remaining methods are kept for BC and potential direct usage within the codebase,
// but are not used when processWorkType delegates to runner classes.
private function backfill(): void {}
private function safeBackfill(): void {}
private function binaries(): void {}
private function safeBinaries(): void {}
private function fixRelNames(): void {}
private function releases(): void {}
public function postProcess(array $releases, int $maxProcess): void {}
private function postProcessAdd(): void {}
private function postProcessNfo(): void {}
private function postProcessMov(): void {}
private function postProcessTv(): void {}
private function processSingle(): void
{
$postProcess = new PostProcess;
$postProcess->processBooks();
$postProcess->processConsoles();
$postProcess->processGames();
$postProcess->processMusic();
$postProcess->processXXX();
}
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();
}
public function logger(string $message): void
{
if (config('nntmux.echocli')) {
echo $message.PHP_EOL;
}
}
public function exit(string $pid): void
{
if (config('nntmux.echocli')) {
$this->colorCli->header(
'Process ID #'.$pid.' has completed.'.PHP_EOL.
'There are '.(max(1, $this->maxProcesses) - 1).' process(es) still active with '.
(--$this->_workCount).' job(s) left in the queue.',
true
);
}
}
}