ForkingService::runWithTiming()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
dl 0
loc 17
rs 9.9
c 1
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
namespace App\Services;
4
5
use App\Models\Settings;
6
use App\Services\Runners\BackfillRunner;
7
use App\Services\Runners\BinariesRunner;
8
use App\Services\Runners\PostProcessRunner;
9
use App\Services\Runners\ReleasesRunner;
10
use Blacklight\ColorCLI;
11
use Blacklight\Nfo;
12
use Illuminate\Support\Facades\DB;
13
use Illuminate\Support\Facades\Log;
14
use Symfony\Component\Process\Process;
15
16
/**
17
 * Service for multiprocessing various tasks.
18
 *
19
 * This service orchestrates parallel processing of usenet indexing tasks
20
 * by delegating to specialized runner classes.
21
 */
22
class ForkingService
23
{
24
    protected ColorCLI $colorCli;
25
26
    protected BackfillRunner $backfillRunner;
27
28
    protected BinariesRunner $binariesRunner;
29
30
    protected ReleasesRunner $releasesRunner;
31
32
    protected PostProcessRunner $postProcessRunner;
33
34
    protected int $maxSize;
35
36
    protected int $minSize;
37
38
    protected int $maxRetries;
39
40
    public function __construct(?ColorCLI $colorCli = null)
41
    {
42
        $this->colorCli = $colorCli ?? new ColorCLI;
43
44
        $this->maxSize = (int) Settings::settingValue('maxsizetoprocessnfo');
45
        $this->minSize = (int) Settings::settingValue('minsizetoprocessnfo');
46
        $this->maxRetries = (int) Settings::settingValue('maxnforetries') >= 0
47
            ? -((int) Settings::settingValue('maxnforetries') + 1)
48
            : Nfo::NFO_UNPROC;
49
        $this->maxRetries = max($this->maxRetries, -8);
50
51
        // Initialize runners
52
        $this->backfillRunner = new BackfillRunner($this->colorCli);
53
        $this->binariesRunner = new BinariesRunner($this->colorCli);
54
        $this->releasesRunner = new ReleasesRunner($this->colorCli);
55
        $this->postProcessRunner = new PostProcessRunner($this->colorCli);
56
    }
57
58
    /**
59
     * Process backfill for all groups with backfill enabled.
60
     */
61
    public function backfill(array $options = []): void
62
    {
63
        $this->runWithTiming('backfill', fn () => $this->backfillRunner->backfill($options));
64
    }
65
66
    /**
67
     * Process safe backfill (ordered by oldest first).
68
     */
69
    public function safeBackfill(): void
70
    {
71
        $this->runWithTiming('safe_backfill', fn () => $this->backfillRunner->safeBackfill());
72
    }
73
74
    /**
75
     * Download binaries (new headers) for all active groups.
76
     */
77
    public function binaries(int $maxPerGroup = 0): void
78
    {
79
        $this->runWithTiming('binaries', fn () => $this->binariesRunner->binaries($maxPerGroup));
80
    }
81
82
    /**
83
     * Process safe binaries (ordered by most recent activity).
84
     */
85
    public function safeBinaries(): void
86
    {
87
        $this->runWithTiming('safe_binaries', fn () => $this->binariesRunner->safeBinaries());
88
    }
89
90
    /**
91
     * Process releases for all groups.
92
     */
93
    public function releases(): void
94
    {
95
        $this->runWithTiming('releases', function () {
96
            $this->releasesRunner->releases();
97
            $this->processReleasesEndWork();
98
        });
99
    }
100
101
    /**
102
     * Update binaries and releases per group.
103
     */
104
    public function updatePerGroup(): void
105
    {
106
        $this->runWithTiming('update_per_group', function () {
107
            $this->releasesRunner->updatePerGroup();
108
            $this->processReleasesEndWork();
109
        });
110
    }
111
112
    /**
113
     * Fix release names using specified mode.
114
     *
115
     * @param  string  $mode  'standard' or 'predbft'
116
     */
117
    public function fixRelNames(string $mode): void
118
    {
119
        $this->runWithTiming("fixRelNames_{$mode}", fn () => $this->releasesRunner->fixRelNames(
120
            $mode,
121
            (int) Settings::settingValue('fixnamesperrun'),
122
            (int) Settings::settingValue('fixnamethreads')
123
        ));
124
    }
125
126
    /**
127
     * Process additional post-processing (preview images, samples, etc).
128
     */
129
    public function processAdditional(): void
130
    {
131
        $this->runWithTiming('postProcess_add', fn () => $this->postProcessRunner->processAdditional());
132
    }
133
134
    /**
135
     * Process NFO files.
136
     */
137
    public function processNfo(): void
138
    {
139
        $this->runWithTiming('postProcess_nfo', fn () => $this->postProcessRunner->processNfo());
140
    }
141
142
    /**
143
     * Process movie metadata.
144
     */
145
    public function processMovies(bool $renamedOnly = false): void
146
    {
147
        $this->runWithTiming('postProcess_mov', fn () => $this->postProcessRunner->processMovies($renamedOnly));
148
    }
149
150
    /**
151
     * Process TV metadata.
152
     */
153
    public function processTv(bool $renamedOnly = false): void
154
    {
155
        $this->runWithTiming('postProcess_tv', function () use ($renamedOnly) {
156
            if ($this->postProcessRunner->hasTvWork($renamedOnly)) {
157
                $this->postProcessRunner->processTv($renamedOnly);
158
            } else {
159
                $this->log('No TV work to do.');
160
            }
161
        });
162
    }
163
164
    /**
165
     * Process anime metadata.
166
     */
167
    public function processAnime(): void
168
    {
169
        $this->runWithTiming('postProcess_ani', fn () => $this->postProcessRunner->processAnime());
170
    }
171
172
    /**
173
     * Process book metadata.
174
     */
175
    public function processBooks(): void
176
    {
177
        $this->runWithTiming('postProcess_ama', fn () => $this->postProcessRunner->processBooks());
178
    }
179
180
    /**
181
     * Generic work type processor for backwards compatibility.
182
     *
183
     * @deprecated Use specific methods instead (e.g., backfill(), binaries(), etc.)
184
     */
185
    public function processWorkType(string $type, array $options = []): void
186
    {
187
        match ($type) {
188
            'backfill' => $this->backfill($options),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->backfill($options) targeting App\Services\ForkingService::backfill() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
189
            'binaries' => $this->binaries((int) ($options[0] ?? 0)),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->binaries((int)$options[0] ?? 0) targeting App\Services\ForkingService::binaries() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
190
            'fixRelNames_standard' => $this->fixRelNames('standard'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fixRelNames('standard') targeting App\Services\ForkingService::fixRelNames() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
191
            'fixRelNames_predbft' => $this->fixRelNames('predbft'),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->fixRelNames('predbft') targeting App\Services\ForkingService::fixRelNames() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
192
            'releases' => $this->releases(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->releases() targeting App\Services\ForkingService::releases() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
193
            'postProcess_add' => $this->processAdditional(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processAdditional() targeting App\Services\ForkingService::processAdditional() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
194
            'postProcess_ani' => $this->processAnime(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processAnime() targeting App\Services\ForkingService::processAnime() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
195
            'postProcess_ama' => $this->processBooks(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processBooks() targeting App\Services\ForkingService::processBooks() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
196
            'postProcess_mov' => $this->processMovies($options[0] ?? false),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processMovies($options[0] ?? false) targeting App\Services\ForkingService::processMovies() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
197
            'postProcess_nfo' => $this->processNfo(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processNfo() targeting App\Services\ForkingService::processNfo() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
198
            'postProcess_tv' => $this->processTv($options[0] ?? false),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->processTv($options[0] ?? false) targeting App\Services\ForkingService::processTv() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
199
            'safe_backfill' => $this->safeBackfill(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->safeBackfill() targeting App\Services\ForkingService::safeBackfill() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
200
            'safe_binaries' => $this->safeBinaries(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->safeBinaries() targeting App\Services\ForkingService::safeBinaries() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
201
            'update_per_group' => $this->updatePerGroup(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->updatePerGroup() targeting App\Services\ForkingService::updatePerGroup() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
202
            default => Log::warning("Unknown work type: {$type}"),
0 ignored issues
show
Bug introduced by
Are you sure the usage of Illuminate\Support\Facad...own work type: '.$type) targeting Illuminate\Support\Facades\Log::warning() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
203
        };
204
    }
205
206
    /**
207
     * Run a task with timing output.
208
     */
209
    protected function runWithTiming(string $taskName, callable $task): void
210
    {
211
        $startTime = now()->timestamp;
212
213
        try {
214
            $task();
215
        } catch (\Throwable $e) {
216
            Log::error("Task {$taskName} failed: {$e->getMessage()}", [
217
                'exception' => $e,
218
            ]);
219
            throw $e;
220
        }
221
222
        if (config('nntmux.echocli')) {
223
            $this->colorCli->header(
224
                "Multi-processing for {$taskName} finished in " . (now()->timestamp - $startTime) .
225
                ' seconds at ' . now()->toRfc2822String() . '.' . PHP_EOL
226
            );
227
        }
228
    }
229
230
    /**
231
     * Process end work for releases (DNR signalling).
232
     */
233
    protected function processReleasesEndWork(): void
234
    {
235
        $count = $this->getReleaseWorkCount();
236
        $command = $this->backfillRunner->buildDnrCommandPublic("releases  {$count}_");
237
        $this->executeCommand($command);
238
    }
239
240
    /**
241
     * Count groups with pending collections.
242
     */
243
    protected function getReleaseWorkCount(): int
244
    {
245
        $groups = DB::select('SELECT id FROM usenet_groups WHERE (active = 1 OR backfill = 1)');
246
        $count = 0;
247
248
        foreach ($groups as $group) {
249
            try {
250
                $query = DB::select(
251
                    sprintf('SELECT id FROM collections WHERE groups_id = %d LIMIT 1', $group->id)
252
                );
253
                if (! empty($query)) {
254
                    $count++;
255
                }
256
            } catch (\PDOException $e) {
257
                if (config('app.debug')) {
258
                    Log::debug($e->getMessage());
259
                }
260
            }
261
        }
262
263
        return $count;
264
    }
265
266
    /**
267
     * Execute a shell command.
268
     */
269
    protected function executeCommand(string $command): string
270
    {
271
        $process = Process::fromShellCommandline($command);
272
        $process->setTimeout(1800);
273
        $process->run(function ($type, $buffer) {
274
            if ($type === Process::ERR) {
275
                echo $buffer;
276
            }
277
        });
278
279
        return $process->getOutput();
280
    }
281
282
    /**
283
     * Log a message to console.
284
     */
285
    protected function log(string $message): void
286
    {
287
        if (config('nntmux.echocli')) {
288
            echo $message . PHP_EOL;
289
        }
290
    }
291
}
292
293