|
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), |
|
|
|
|
|
|
189
|
|
|
'binaries' => $this->binaries((int) ($options[0] ?? 0)), |
|
|
|
|
|
|
190
|
|
|
'fixRelNames_standard' => $this->fixRelNames('standard'), |
|
|
|
|
|
|
191
|
|
|
'fixRelNames_predbft' => $this->fixRelNames('predbft'), |
|
|
|
|
|
|
192
|
|
|
'releases' => $this->releases(), |
|
|
|
|
|
|
193
|
|
|
'postProcess_add' => $this->processAdditional(), |
|
|
|
|
|
|
194
|
|
|
'postProcess_ani' => $this->processAnime(), |
|
|
|
|
|
|
195
|
|
|
'postProcess_ama' => $this->processBooks(), |
|
|
|
|
|
|
196
|
|
|
'postProcess_mov' => $this->processMovies($options[0] ?? false), |
|
|
|
|
|
|
197
|
|
|
'postProcess_nfo' => $this->processNfo(), |
|
|
|
|
|
|
198
|
|
|
'postProcess_tv' => $this->processTv($options[0] ?? false), |
|
|
|
|
|
|
199
|
|
|
'safe_backfill' => $this->safeBackfill(), |
|
|
|
|
|
|
200
|
|
|
'safe_binaries' => $this->safeBinaries(), |
|
|
|
|
|
|
201
|
|
|
'update_per_group' => $this->updatePerGroup(), |
|
|
|
|
|
|
202
|
|
|
default => Log::warning("Unknown work type: {$type}"), |
|
|
|
|
|
|
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
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
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.