NameFixingService::fixNamesWithMediaMovieName()   B
last analyzed

Complexity

Conditions 6
Paths 18

Size

Total Lines 43
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
dl 0
loc 43
rs 8.7857
c 1
b 0
f 0
cc 6
nc 18
nop 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Services\NameFixing;
6
7
use App\Models\Category;
8
use App\Models\Release;
9
use App\Services\NameFixing\Contracts\NameSourceFixerInterface;
10
use App\Services\NameFixing\DTO\NameFixResult;
11
use App\Services\NameFixing\Extractors\NfoNameExtractor;
12
use App\Services\NameFixing\Extractors\FileNameExtractor;
13
use App\Services\Search\ElasticSearchService;
14
use App\Services\Search\ManticoreSearchService;
15
use Blacklight\ColorCLI;
16
use Illuminate\Support\Arr;
17
18
/**
19
 * Main service for name fixing operations.
20
 *
21
 * Orchestrates the various name fixing sources (NFO, Files, CRC, SRR, etc.)
22
 * and handles the overall processing flow.
23
 */
24
class NameFixingService
25
{
26
    // Constants for name fixing status
27
    public const PROC_NFO_NONE = 0;
28
    public const PROC_NFO_DONE = 1;
29
    public const PROC_FILES_NONE = 0;
30
    public const PROC_FILES_DONE = 1;
31
    public const PROC_PAR2_NONE = 0;
32
    public const PROC_PAR2_DONE = 1;
33
    public const PROC_UID_NONE = 0;
34
    public const PROC_UID_DONE = 1;
35
    public const PROC_HASH16K_NONE = 0;
36
    public const PROC_HASH16K_DONE = 1;
37
    public const PROC_SRR_NONE = 0;
38
    public const PROC_SRR_DONE = 1;
39
    public const PROC_CRC_NONE = 0;
40
    public const PROC_CRC_DONE = 1;
41
42
    // Constants for overall rename status
43
    public const IS_RENAMED_NONE = 0;
44
    public const IS_RENAMED_DONE = 1;
45
46
    protected ReleaseUpdateService $updateService;
47
    protected NameCheckerService $checkerService;
48
    protected NfoNameExtractor $nfoExtractor;
49
    protected FileNameExtractor $fileExtractor;
50
    protected FileNameCleaner $fileNameCleaner;
51
    protected FilePrioritizer $filePrioritizer;
52
    protected ManticoreSearchService $manticore;
53
    protected ElasticSearchService $elasticsearch;
54
    protected ColorCLI $colorCLI;
55
    protected bool $echoOutput;
56
57
    protected string $othercats;
58
    protected string $timeother;
59
    protected string $timeall;
60
    protected string $fullother;
61
    protected string $fullall;
62
63
    protected int $_totalReleases = 0;
64
65
    public function __construct(
66
        ?ReleaseUpdateService $updateService = null,
67
        ?NameCheckerService $checkerService = null,
68
        ?NfoNameExtractor $nfoExtractor = null,
69
        ?FileNameExtractor $fileExtractor = null,
70
        ?FileNameCleaner $fileNameCleaner = null,
71
        ?FilePrioritizer $filePrioritizer = null,
72
        ?ManticoreSearchService $manticore = null,
73
        ?ElasticSearchService $elasticsearch = null,
74
        ?ColorCLI $colorCLI = null
75
    ) {
76
        $this->updateService = $updateService ?? new ReleaseUpdateService();
77
        $this->checkerService = $checkerService ?? new NameCheckerService();
78
        $this->nfoExtractor = $nfoExtractor ?? new NfoNameExtractor();
79
        $this->fileExtractor = $fileExtractor ?? new FileNameExtractor();
80
        $this->fileNameCleaner = $fileNameCleaner ?? new FileNameCleaner();
81
        $this->filePrioritizer = $filePrioritizer ?? new FilePrioritizer();
82
        $this->manticore = $manticore ?? app(ManticoreSearchService::class);
83
        $this->elasticsearch = $elasticsearch ?? app(ElasticSearchService::class);
84
        $this->colorCLI = $colorCLI ?? new ColorCLI();
85
        $this->echoOutput = config('nntmux.echocli');
86
87
        $this->othercats = implode(',', Category::OTHERS_GROUP);
88
        $this->timeother = sprintf(' AND rel.adddate > (NOW() - INTERVAL 6 HOUR) AND rel.categories_id IN (%s) GROUP BY rel.id ORDER BY postdate DESC', $this->othercats);
89
        $this->timeall = ' AND rel.adddate > (NOW() - INTERVAL 6 HOUR) GROUP BY rel.id ORDER BY postdate DESC';
90
        $this->fullother = sprintf(' AND rel.categories_id IN (%s) GROUP BY rel.id', $this->othercats);
91
        $this->fullall = '';
92
    }
93
94
    /**
95
     * Fix names using NFO content.
96
     */
97
    public function fixNamesWithNfo(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
98
    {
99
        $this->echoStartMessage($time, '.nfo files');
100
        $type = 'NFO, ';
101
102
        $preId = false;
103
        if ($cats === 3) {
104
            $query = sprintf(
105
                'SELECT rel.id AS releases_id, rel.fromname
106
                FROM releases rel
107
                INNER JOIN release_nfos nfo ON (nfo.releases_id = rel.id)
108
                WHERE rel.predb_id = 0'
109
            );
110
            $cats = 2;
111
            $preId = true;
112
        } else {
113
            $query = sprintf(
114
                'SELECT rel.id AS releases_id, rel.fromname
115
                FROM releases rel
116
                INNER JOIN release_nfos nfo ON (nfo.releases_id = rel.id)
117
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
118
                AND rel.predb_id = 0
119
                AND rel.proc_nfo = %d',
120
                self::IS_RENAMED_NONE,
121
                Category::OTHER_MISC,
122
                Category::OTHER_HASHED,
123
                self::PROC_NFO_NONE
124
            );
125
        }
126
127
        $releases = $this->getReleases($time, $cats, $query);
128
        $total = $releases->count();
129
130
        if ($total > 0) {
131
            $this->_totalReleases = $total;
132
            $this->colorCLI->info(number_format($total) . ' releases to process.');
133
134
            foreach ($releases as $rel) {
135
                $releaseRow = Release::fromQuery(
136
                    sprintf(
137
                        'SELECT nfo.releases_id AS nfoid, rel.groups_id, rel.fromname, rel.categories_id, rel.name, rel.searchname,
138
                            UNCOMPRESS(nfo) AS textstring, rel.id AS releases_id
139
                        FROM releases rel
140
                        INNER JOIN release_nfos nfo ON (nfo.releases_id = rel.id)
141
                        WHERE rel.id = %d LIMIT 1',
142
                        $rel->releases_id
143
                    )
144
                );
145
146
                $this->updateService->incrementChecked();
147
148
                // Ignore encrypted NFOs
149
                if (preg_match('/^=newz\[NZB\]=\w+/', $releaseRow[0]->textstring)) {
150
                    $this->updateService->updateSingleColumn('proc_nfo', self::PROC_NFO_DONE, $rel->releases_id);
151
                    continue;
152
                }
153
154
                $this->updateService->reset();
155
156
                // Try NFO extraction
157
                $nfoResult = $this->nfoExtractor->extractFromNfo($releaseRow[0]->textstring);
158
                if ($nfoResult !== null) {
159
                    $this->updateService->updateRelease(
160
                        $releaseRow[0],
161
                        $nfoResult->newName,
162
                        'nfoCheck: ' . $nfoResult->method,
163
                        $echo,
164
                        $type,
165
                        $nameStatus,
166
                        $show
167
                    );
168
                }
169
170
                // If NFO extraction didn't work, try pattern checkers
171
                if (!$this->updateService->matched) {
172
                    $this->checkWithPatternMatchers($releaseRow[0], $echo, $type, $nameStatus, $show, $preId);
173
                }
174
175
                $this->echoRenamed($show);
176
            }
177
            $this->echoFoundCount($echo, ' NFO\'s');
178
        } else {
179
            $this->colorCLI->info('Nothing to fix.');
180
        }
181
    }
182
183
    /**
184
     * Fix names using file names.
185
     */
186
    public function fixNamesWithFiles(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
187
    {
188
        $this->echoStartMessage($time, 'file names');
189
        $type = 'Filenames, ';
190
191
        $preId = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $preId is dead and can be removed.
Loading history...
192
        if ($cats === 3) {
193
            $query = sprintf(
194
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
195
                    rf.releases_id AS fileid, rel.id AS releases_id
196
                FROM releases rel
197
                INNER JOIN release_files rf ON rf.releases_id = rel.id
198
                WHERE predb_id = 0'
199
            );
200
            $cats = 2;
201
            $preId = true;
202
        } else {
203
            $query = sprintf(
204
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
205
                    rf.releases_id AS fileid, rel.id AS releases_id
206
                FROM releases rel
207
                INNER JOIN release_files rf ON rf.releases_id = rel.id
208
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
209
                AND rel.predb_id = 0
210
                AND proc_files = %d',
211
                self::IS_RENAMED_NONE,
212
                Category::OTHER_MISC,
213
                Category::OTHER_HASHED,
214
                self::PROC_FILES_NONE
215
            );
216
        }
217
218
        $releases = $this->getReleases($time, $cats, $query);
219
        $total = $releases->count();
220
221
        if ($total > 0) {
222
            $this->_totalReleases = $total;
223
            $this->colorCLI->info(number_format($total) . ' file names to process.');
224
225
            // Group files by release
226
            $releaseFiles = [];
227
            foreach ($releases as $release) {
228
                $releaseId = $release->releases_id;
229
                if (!isset($releaseFiles[$releaseId])) {
230
                    $releaseFiles[$releaseId] = [
231
                        'release' => $release,
232
                        'files' => [],
233
                    ];
234
                }
235
                $releaseFiles[$releaseId]['files'][] = $release->textstring;
236
            }
237
238
            foreach ($releaseFiles as $releaseId => $data) {
239
                $this->updateService->reset();
240
                $this->updateService->incrementChecked();
241
242
                // Prioritize files for matching
243
                $prioritizedFiles = $this->filePrioritizer->prioritizeForMatching($data['files']);
244
245
                foreach ($prioritizedFiles as $filename) {
246
                    $release = clone $data['release'];
247
                    $release->textstring = $filename;
248
249
                    // Try file name extraction
250
                    $fileResult = $this->fileExtractor->extractFromFile($filename);
251
                    if ($fileResult !== null) {
252
                        $this->updateService->updateRelease(
253
                            $release,
254
                            $fileResult->newName,
255
                            'fileCheck: ' . $fileResult->method,
256
                            $echo,
257
                            $type,
258
                            $nameStatus,
259
                            $show
260
                        );
261
                    }
262
263
                    // If not matched, try PreDB search
264
                    if (!$this->updateService->matched) {
265
                        $this->preDbFileCheck($release, $echo, $type, $nameStatus, $show);
266
                    }
267
268
                    if (!$this->updateService->matched) {
269
                        $this->preDbTitleCheck($release, $echo, $type, $nameStatus, $show);
270
                    }
271
272
                    if ($this->updateService->matched) {
273
                        break;
274
                    }
275
                }
276
277
                $this->echoRenamed($show);
278
            }
279
280
            $this->echoFoundCount($echo, ' files');
281
        } else {
282
            $this->colorCLI->info('Nothing to fix.');
283
        }
284
    }
285
286
    /**
287
     * Fix names using SRR files.
288
     */
289
    public function fixNamesWithSrr(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
290
    {
291
        $this->echoStartMessage($time, 'SRR file names');
292
        $type = 'SRR, ';
293
294
        if ($cats === 3) {
295
            $query = sprintf(
296
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
297
                    rf.releases_id AS fileid, rel.id AS releases_id
298
                FROM releases rel
299
                INNER JOIN release_files rf ON (rf.releases_id = rel.id)
300
                WHERE predb_id = 0
301
                AND (rf.name LIKE %s OR rf.name LIKE %s)',
302
                escapeString('%.srr'),
303
                escapeString('%.srs')
304
            );
305
            $cats = 2;
306
        } else {
307
            $query = sprintf(
308
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
309
                    rf.releases_id AS fileid, rel.id AS releases_id
310
                FROM releases rel
311
                INNER JOIN release_files rf ON (rf.releases_id = rel.id)
312
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
313
                AND rel.predb_id = 0
314
                AND (rf.name LIKE %s OR rf.name LIKE %s)
315
                AND rel.proc_srr = %d',
316
                self::IS_RENAMED_NONE,
317
                Category::OTHER_MISC,
318
                Category::OTHER_HASHED,
319
                escapeString('%.srr'),
320
                escapeString('%.srs'),
321
                self::PROC_SRR_NONE
322
            );
323
        }
324
325
        $releases = $this->getReleases($time, $cats, $query);
326
        $total = $releases->count();
327
328
        if ($total > 0) {
329
            $this->_totalReleases = $total;
330
            $this->colorCLI->info(number_format($total) . ' srr file extensions to process.');
331
332
            foreach ($releases as $release) {
333
                $this->updateService->reset();
334
                $this->updateService->incrementChecked();
335
336
                $this->srrNameCheck($release, $echo, $type, $nameStatus, $show);
337
                $this->echoRenamed($show);
338
            }
339
340
            $this->echoFoundCount($echo, ' files');
341
        } else {
342
            $this->colorCLI->info('Nothing to fix.');
343
        }
344
    }
345
346
    /**
347
     * Fix names using CRC32 hashes.
348
     */
349
    public function fixNamesWithCrc(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
350
    {
351
        $this->echoStartMessage($time, 'CRC32');
352
        $type = 'CRC32, ';
353
354
        $preId = false;
355
        if ($cats === 3) {
356
            $query = sprintf(
357
                'SELECT rf.crc32 AS textstring, rf.name AS filename, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id, rel.size as relsize,
358
                    rf.releases_id AS fileid, rel.id AS releases_id
359
                FROM releases rel
360
                INNER JOIN release_files rf ON rf.releases_id = rel.id
361
                WHERE predb_id = 0
362
                AND rf.crc32 != \'\'
363
                AND rf.crc32 IS NOT NULL'
364
            );
365
            $cats = 2;
366
            $preId = true;
367
        } else {
368
            $query = sprintf(
369
                'SELECT rf.crc32 AS textstring, rf.name AS filename, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id, rel.size as relsize,
370
                    rf.releases_id AS fileid, rel.id AS releases_id
371
                FROM releases rel
372
                INNER JOIN release_files rf ON rf.releases_id = rel.id
373
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
374
                AND rel.predb_id = 0
375
                AND rel.proc_crc32 = %d
376
                AND rf.crc32 != \'\'
377
                AND rf.crc32 IS NOT NULL',
378
                self::IS_RENAMED_NONE,
379
                Category::OTHER_MISC,
380
                Category::OTHER_HASHED,
381
                self::PROC_CRC_NONE
382
            );
383
        }
384
385
        $releases = $this->getReleases($time, $cats, $query);
386
        $total = $releases->count();
387
388
        if ($total > 0) {
389
            $this->_totalReleases = $total;
390
            $this->colorCLI->info(number_format($total) . ' CRC32\'s to process.');
391
392
            // Group by release
393
            $releasesCrc = [];
394
            foreach ($releases as $release) {
395
                $releaseId = $release->releases_id;
396
                if (!isset($releasesCrc[$releaseId])) {
397
                    $releasesCrc[$releaseId] = [
398
                        'release' => $release,
399
                        'crcs' => [],
400
                    ];
401
                }
402
                if (!empty($release->textstring)) {
403
                    $priority = $this->filePrioritizer->getCrcPriority($release->filename ?? '');
404
                    $releasesCrc[$releaseId]['crcs'][$priority][] = $release->textstring;
405
                }
406
            }
407
408
            foreach ($releasesCrc as $releaseId => $data) {
409
                $this->updateService->reset();
410
                $this->updateService->incrementChecked();
411
412
                ksort($data['crcs']);
413
                foreach ($data['crcs'] as $crcs) {
414
                    foreach ($crcs as $crc) {
415
                        $release = clone $data['release'];
416
                        $release->textstring = $crc;
417
418
                        $this->crcCheck($release, $echo, $type, $nameStatus, $show, $preId);
419
420
                        if ($this->updateService->matched) {
421
                            break 2;
422
                        }
423
                    }
424
                }
425
426
                $this->echoRenamed($show);
427
            }
428
429
            $this->echoFoundCount($echo, ' crc32\'s');
430
        } else {
431
            $this->colorCLI->info('Nothing to fix.');
432
        }
433
    }
434
435
    /**
436
     * Fix names using Media info unique IDs.
437
     */
438
    public function fixNamesWithMedia(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
439
    {
440
        $type = 'UID, ';
441
        $this->echoStartMessage($time, 'mediainfo Unique_IDs');
442
443
        if ($cats === 3) {
444
            $query = sprintf(
445
                'SELECT rel.id AS releases_id, rel.size AS relsize, rel.groups_id, rel.fromname, rel.categories_id,
446
                    rel.name, rel.name AS textstring, rel.predb_id, rel.searchname,
447
                    ru.unique_id AS uid
448
                FROM releases rel
449
                LEFT JOIN media_infos ru ON ru.releases_id = rel.id
450
                WHERE ru.releases_id IS NOT NULL
451
                AND rel.predb_id = 0'
452
            );
453
            $cats = 2;
454
        } else {
455
            $query = sprintf(
456
                'SELECT rel.id AS releases_id, rel.size AS relsize, rel.groups_id, rel.fromname, rel.categories_id,
457
                    rel.name, rel.name AS textstring, rel.predb_id, rel.searchname,
458
                    ru.unique_id AS uid
459
                FROM releases rel
460
                LEFT JOIN media_infos ru ON ru.releases_id = rel.id
461
                WHERE ru.releases_id IS NOT NULL
462
                AND (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
463
                AND rel.predb_id = 0
464
                AND rel.proc_uid = %d',
465
                self::IS_RENAMED_NONE,
466
                Category::OTHER_MISC,
467
                Category::OTHER_HASHED,
468
                self::PROC_UID_NONE
469
            );
470
        }
471
472
        $releases = $this->getReleases($time, $cats, $query);
473
        $total = $releases->count();
474
475
        if ($total > 0) {
476
            $this->_totalReleases = $total;
477
            $this->colorCLI->info(number_format($total) . ' unique ids to process.');
478
479
            foreach ($releases as $rel) {
480
                $this->updateService->reset();
481
                $this->updateService->incrementChecked();
482
                $this->uidCheck($rel, $echo, $type, $nameStatus, $show);
483
                $this->echoRenamed($show);
484
            }
485
486
            $this->echoFoundCount($echo, ' UID\'s');
487
        } else {
488
            $this->colorCLI->info('Nothing to fix.');
489
        }
490
    }
491
492
    /**
493
     * Fix names using PAR2 hash_16K.
494
     */
495
    public function fixNamesWithParHash(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
496
    {
497
        $type = 'PAR2 hash, ';
498
        $this->echoStartMessage($time, 'PAR2 hash_16K');
499
500
        if ($cats === 3) {
501
            $query = sprintf(
502
                'SELECT rel.id AS releases_id, rel.size AS relsize, rel.groups_id, rel.fromname, rel.categories_id,
503
                    rel.name, rel.name AS textstring, rel.predb_id, rel.searchname,
504
                    IFNULL(ph.hash, \'\') AS hash
505
                FROM releases rel
506
                LEFT JOIN par_hashes ph ON ph.releases_id = rel.id
507
                WHERE ph.hash != \'\'
508
                AND rel.predb_id = 0'
509
            );
510
            $cats = 2;
511
        } else {
512
            $query = sprintf(
513
                'SELECT rel.id AS releases_id, rel.size AS relsize, rel.groups_id, rel.fromname, rel.categories_id,
514
                    rel.name, rel.name AS textstring, rel.predb_id, rel.searchname,
515
                    IFNULL(ph.hash, \'\') AS hash
516
                FROM releases rel
517
                LEFT JOIN par_hashes ph ON ph.releases_id = rel.id
518
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
519
                AND rel.predb_id = 0
520
                AND ph.hash != \'\'
521
                AND rel.proc_hash16k = %d',
522
                self::IS_RENAMED_NONE,
523
                Category::OTHER_MISC,
524
                Category::OTHER_HASHED,
525
                self::PROC_HASH16K_NONE
526
            );
527
        }
528
529
        $releases = $this->getReleases($time, $cats, $query);
530
        $total = $releases->count();
531
532
        if ($total > 0) {
533
            $this->_totalReleases = $total;
534
            $this->colorCLI->info(number_format($total) . ' hash_16K to process.');
535
536
            foreach ($releases as $rel) {
537
                $this->updateService->reset();
538
                $this->updateService->incrementChecked();
539
                $this->hashCheck($rel, $echo, $type, $nameStatus, $show);
540
                $this->echoRenamed($show);
541
            }
542
543
            $this->echoFoundCount($echo, ' hashes');
544
        } else {
545
            $this->colorCLI->info('Nothing to fix.');
546
        }
547
    }
548
549
    /**
550
     * Check with pattern matchers (TV, Movie, Game, App).
551
     */
552
    protected function checkWithPatternMatchers(object $release, bool $echo, string $type, bool $nameStatus, bool $show, bool $preId): void
553
    {
554
        // Check for PreDB match first
555
        $preDbMatch = $this->updateService->checkPreDbMatch($release, $release->textstring);
556
        if ($preDbMatch !== null) {
557
            $this->updateService->updateRelease(
558
                $release,
559
                $preDbMatch['title'],
560
                'preDB: Match',
561
                $echo,
562
                $type,
563
                $nameStatus,
564
                $show,
565
                $preDbMatch['id']
566
            );
567
            return;
568
        }
569
570
        if ($preId) {
571
            return;
572
        }
573
574
        // Try pattern checkers
575
        $result = $this->checkerService->check($release, $release->textstring);
576
        if ($result !== null) {
577
            $this->updateService->updateRelease(
578
                $release,
579
                $result->newName,
580
                $result->getFormattedMethod(),
581
                $echo,
582
                $type,
583
                $nameStatus,
584
                $show
585
            );
586
        }
587
    }
588
589
    /**
590
     * Check SRR file for release name.
591
     */
592
    protected function srrNameCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
593
    {
594
        $extractedName = null;
595
596
        if (preg_match('/^(.+)\.srr$/i', $release->textstring, $hit)) {
597
            $extractedName = $hit[1];
598
        } elseif (preg_match('/^(.+)\.srs$/i', $release->textstring, $hit)) {
599
            $extractedName = $hit[1];
600
        }
601
602
        if ($extractedName !== null) {
603
            if (preg_match('/[\\\\\/]([^\\\\\/]+)$/', $extractedName, $pathMatch)) {
604
                $extractedName = $pathMatch[1];
605
            }
606
607
            if (preg_match(ReleaseUpdateService::PREDB_REGEX, $extractedName)) {
608
                $this->updateService->updateRelease(
609
                    $release,
610
                    $extractedName,
611
                    'fileCheck: SRR extension',
612
                    $echo,
613
                    $type,
614
                    $nameStatus,
615
                    $show
616
                );
617
                return true;
618
            }
619
        }
620
621
        $this->updateService->updateSingleColumn('proc_srr', self::PROC_SRR_DONE, $release->releases_id);
622
        return false;
623
    }
624
625
    /**
626
     * Check CRC32 for matches.
627
     */
628
    protected function crcCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show, bool $preId): bool
0 ignored issues
show
Unused Code introduced by
The parameter $preId is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

628
    protected function crcCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show, /** @scrutinizer ignore-unused */ bool $preId): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
629
    {
630
        if ($release->textstring === '') {
631
            $this->updateService->updateSingleColumn('proc_crc32', self::PROC_CRC_DONE, $release->releases_id);
632
            return false;
633
        }
634
635
        $result = Release::fromQuery(
636
            sprintf(
637
                'SELECT rf.crc32, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id, rel.size as relsize, rel.predb_id as predb_id,
638
                    rf.releases_id AS fileid, rel.id AS releases_id
639
                FROM releases rel
640
                LEFT JOIN release_files rf ON rf.releases_id = rel.id
641
                WHERE rel.predb_id > 0
642
                AND rf.crc32 = %s',
643
                escapeString($release->textstring)
644
            )
645
        );
646
647
        foreach ($result as $res) {
648
            $floor = round(($res->relsize - $release->relsize) / $res->relsize * 100, 1);
649
            if ($floor >= -5 && $floor <= 5) {
650
                $this->updateService->updateRelease(
651
                    $release,
652
                    $res->searchname,
653
                    'crcCheck: CRC32',
654
                    $echo,
655
                    $type,
656
                    $nameStatus,
657
                    $show,
658
                    $res->predb_id
659
                );
660
                return true;
661
            }
662
        }
663
664
        $this->updateService->updateSingleColumn('proc_crc32', self::PROC_CRC_DONE, $release->releases_id);
665
        return false;
666
    }
667
668
    /**
669
     * Check UID for matches.
670
     */
671
    protected function uidCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
672
    {
673
        if (empty($release->uid)) {
674
            $this->updateService->updateSingleColumn('proc_uid', self::PROC_UID_DONE, $release->releases_id);
675
            return false;
676
        }
677
678
        $result = Release::fromQuery(sprintf(
679
            'SELECT r.id AS releases_id, r.size AS relsize, r.name AS textstring, r.searchname, r.fromname, r.predb_id
680
            FROM releases r
681
            LEFT JOIN media_infos ru ON ru.releases_id = r.id
682
            WHERE ru.releases_id IS NOT NULL
683
            AND ru.unique_id = %s
684
            AND ru.releases_id != %d
685
            AND (r.predb_id > 0 OR r.anidbid > 0 OR r.fromname = %s)',
686
            escapeString($release->uid),
687
            $release->releases_id,
688
            escapeString('[email protected] (EF)')
689
        ));
690
691
        foreach ($result as $res) {
692
            $floor = round(($res->relsize - $release->relsize) / $res->relsize * 100, 1);
693
            if ($floor >= -10 && $floor <= 10) {
694
                $this->updateService->updateRelease(
695
                    $release,
696
                    $res->searchname,
697
                    'uidCheck: Unique_ID',
698
                    $echo,
699
                    $type,
700
                    $nameStatus,
701
                    $show,
702
                    $res->predb_id
703
                );
704
                return true;
705
            }
706
        }
707
708
        $this->updateService->updateSingleColumn('proc_uid', self::PROC_UID_DONE, $release->releases_id);
709
        return false;
710
    }
711
712
    /**
713
     * Check PAR2 hash for matches.
714
     */
715
    protected function hashCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
716
    {
717
        $result = Release::fromQuery(sprintf(
718
            'SELECT r.id AS releases_id, r.size AS relsize, r.name AS textstring, r.searchname, r.fromname, r.predb_id
719
            FROM releases r
720
            LEFT JOIN par_hashes ph ON ph.releases_id = r.id
721
            WHERE ph.hash = %s
722
            AND ph.releases_id != %d
723
            AND (r.predb_id > 0 OR r.anidbid > 0)',
724
            escapeString($release->hash),
725
            $release->releases_id
726
        ));
727
728
        foreach ($result as $res) {
729
            $floor = round(($res->relsize - $release->relsize) / $res->relsize * 100, 1);
730
            if ($floor >= -5 && $floor <= 5) {
731
                $this->updateService->updateRelease(
732
                    $release,
733
                    $res->searchname,
734
                    'hashCheck: PAR2 hash_16K',
735
                    $echo,
736
                    $type,
737
                    $nameStatus,
738
                    $show,
739
                    $res->predb_id
740
                );
741
                return true;
742
            }
743
        }
744
745
        $this->updateService->updateSingleColumn('proc_hash16k', self::PROC_HASH16K_DONE, $release->releases_id);
746
        return false;
747
    }
748
749
    /**
750
     * Check PreDB for filename matches.
751
     */
752
    protected function preDbFileCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
753
    {
754
        $fileName = $this->fileNameCleaner->cleanForMatching($release->textstring);
755
756
        if (empty($fileName)) {
757
            return false;
758
        }
759
760
        if (config('nntmux.elasticsearch_enabled') === true) {
761
            $results = $this->elasticsearch->searchPreDb($fileName);
762
            foreach ($results as $hit) {
763
                if (!empty($hit)) {
764
                    $this->updateService->updateRelease(
765
                        $release,
766
                        $hit['title'],
767
                        'PreDb: Filename match',
768
                        $echo,
769
                        $type,
770
                        $nameStatus,
771
                        $show,
772
                        $hit['id']
773
                    );
774
                    return true;
775
                }
776
            }
777
        } else {
778
            $predbSearch = Arr::get($this->manticore->searchIndexes('predb_rt', $fileName, ['filename', 'title']), 'data');
779
            if (!empty($predbSearch)) {
780
                foreach ($predbSearch as $hit) {
781
                    if (!empty($hit)) {
782
                        $this->updateService->updateRelease(
783
                            $release,
784
                            $hit['title'],
785
                            'PreDb: Filename match',
786
                            $echo,
787
                            $type,
788
                            $nameStatus,
789
                            $show
790
                        );
791
                        return true;
792
                    }
793
                }
794
            }
795
        }
796
797
        return false;
798
    }
799
800
    /**
801
     * Check PreDB for title matches.
802
     */
803
    protected function preDbTitleCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
804
    {
805
        $fileName = $this->fileNameCleaner->cleanForMatching($release->textstring);
806
807
        if (empty($fileName)) {
808
            return false;
809
        }
810
811
        if (config('nntmux.elasticsearch_enabled') === true) {
812
            $results = $this->elasticsearch->searchPreDb($fileName);
813
            foreach ($results as $hit) {
814
                if (!empty($hit)) {
815
                    $this->updateService->updateRelease(
816
                        $release,
817
                        $hit['title'],
818
                        'PreDb: Title match',
819
                        $echo,
820
                        $type,
821
                        $nameStatus,
822
                        $show,
823
                        $hit['id']
824
                    );
825
                    return true;
826
                }
827
            }
828
        } else {
829
            $results = Arr::get($this->manticore->searchIndexes('predb_rt', $fileName, ['title']), 'data');
830
            if (!empty($results)) {
831
                foreach ($results as $hit) {
832
                    if (!empty($hit)) {
833
                        $this->updateService->updateRelease(
834
                            $release,
835
                            $hit['title'],
836
                            'PreDb: Title match',
837
                            $echo,
838
                            $type,
839
                            $nameStatus,
840
                            $show
841
                        );
842
                        return true;
843
                    }
844
                }
845
            }
846
        }
847
848
        return false;
849
    }
850
851
    /**
852
     * Get releases based on time and category parameters.
853
     */
854
    protected function getReleases(int $time, int $cats, string $query, int $limit = 0): \Illuminate\Database\Eloquent\Collection|bool
855
    {
856
        $releases = false;
857
        $queryLimit = ($limit === 0) ? '' : ' LIMIT ' . $limit;
858
859
        if ($time === 1 && $cats === 1) {
860
            $releases = Release::fromQuery($query . $this->timeother . $queryLimit);
861
        }
862
        if ($time === 1 && $cats === 2) {
863
            $releases = Release::fromQuery($query . $this->timeall . $queryLimit);
864
        }
865
        if ($time === 2 && $cats === 1) {
866
            $releases = Release::fromQuery($query . $this->fullother . $queryLimit);
867
        }
868
        if ($time === 2 && $cats === 2) {
869
            $releases = Release::fromQuery($query . $this->fullall . $queryLimit);
870
        }
871
872
        return $releases;
873
    }
874
875
    /**
876
     * Echo start message.
877
     */
878
    protected function echoStartMessage(int $time, string $type): void
879
    {
880
        $this->colorCLI->info(
881
            sprintf(
882
                'Fixing search names %s using %s.',
883
                ($time === 1 ? 'in the past 6 hours' : 'since the beginning'),
884
                $type
885
            )
886
        );
887
    }
888
889
    /**
890
     * Echo found count.
891
     */
892
    protected function echoFoundCount(bool $echo, string $type): void
893
    {
894
        $stats = $this->updateService->getStats();
895
        if ($echo === true) {
896
            $this->colorCLI->info(
897
                PHP_EOL .
898
                number_format($stats['fixed']) .
899
                ' releases have had their names changed out of: ' .
900
                number_format($stats['checked']) .
901
                $type . '.'
902
            );
903
        } else {
904
            $this->colorCLI->info(
905
                PHP_EOL .
906
                number_format($stats['fixed']) .
907
                ' releases could have their names changed. ' .
908
                number_format($stats['checked']) .
909
                $type . ' were checked.'
910
            );
911
        }
912
    }
913
914
    /**
915
     * Echo renamed progress.
916
     */
917
    protected function echoRenamed(bool $show): void
918
    {
919
        $stats = $this->updateService->getStats();
920
921
        // Show milestone message every 500 releases
922
        if ($stats['checked'] % 500 === 0 && $stats['checked'] > 0) {
923
            $this->colorCLI->alternate(PHP_EOL . number_format($stats['checked']) . ' files processed.' . PHP_EOL);
924
        }
925
926
        // Show active counter on the same line (overwrites previous)
927
        if ($show === true) {
928
            $percent = $this->_totalReleases > 0
929
                ? round(($stats['checked'] / $this->_totalReleases) * 100, 1)
930
                : 0;
931
932
            // Use carriage return to overwrite the same line
933
            echo "\rRenamed: " . number_format($stats['fixed']) .
934
                 ' | Processed: ' . number_format($stats['checked']) .
935
                 '/' . number_format($this->_totalReleases) .
936
                 ' (' . $percent . '%)    ';
937
        }
938
    }
939
940
    /**
941
     * Get the update service.
942
     */
943
    public function getUpdateService(): ReleaseUpdateService
944
    {
945
        return $this->updateService;
946
    }
947
948
    /**
949
     * Get the checker service.
950
     */
951
    public function getCheckerService(): NameCheckerService
952
    {
953
        return $this->checkerService;
954
    }
955
956
    /**
957
     * Fix names using PAR2 files (requires NNTP connection).
958
     */
959
    public function fixNamesWithPar2(int $time, bool $echo, int $cats, bool $nameStatus, bool $show, \Blacklight\NNTP $nntp): void
0 ignored issues
show
Unused Code introduced by
The parameter $nntp is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

959
    public function fixNamesWithPar2(int $time, bool $echo, int $cats, bool $nameStatus, bool $show, /** @scrutinizer ignore-unused */ \Blacklight\NNTP $nntp): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
960
    {
961
        $this->echoStartMessage($time, 'par2 files');
962
963
        if ($cats === 3) {
964
            $query = sprintf(
965
                'SELECT rel.id AS releases_id, rel.guid, rel.groups_id, rel.fromname
966
                FROM releases rel
967
                WHERE rel.predb_id = 0'
968
            );
969
            $cats = 2;
970
        } else {
971
            $query = sprintf(
972
                'SELECT rel.id AS releases_id, rel.guid, rel.groups_id, rel.fromname
973
                FROM releases rel
974
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
975
                AND rel.predb_id = 0
976
                AND rel.proc_par2 = %d',
977
                self::IS_RENAMED_NONE,
978
                Category::OTHER_MISC,
979
                Category::OTHER_HASHED,
980
                self::PROC_PAR2_NONE
981
            );
982
        }
983
984
        $releases = $this->getReleases($time, $cats, $query);
985
        $total = $releases ? $releases->count() : 0;
986
987
        if ($total > 0) {
988
            $this->_totalReleases = $total;
989
            $this->colorCLI->info(number_format($total) . ' releases to process.');
990
            $nzbContents = new \Blacklight\NZBContents();
991
992
            foreach ($releases as $release) {
993
                if ($nzbContents->checkPAR2($release->guid, $release->releases_id, $release->groups_id, (int) $nameStatus, (int) $show)) {
994
                    $this->updateService->fixed++;
995
                }
996
997
                $this->updateService->incrementChecked();
998
                $this->echoRenamed($show);
999
            }
1000
            $this->echoFoundCount($echo, ' files');
1001
        } else {
1002
            $this->colorCLI->info('Nothing to fix.');
1003
        }
1004
    }
1005
1006
    /**
1007
     * Fix XXX release names using specific file names.
1008
     */
1009
    public function fixXXXNamesWithFiles(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
1010
    {
1011
        $this->echoStartMessage($time, 'file names');
1012
        $type = 'Filenames, ';
1013
1014
        if ($cats === 3) {
1015
            $query = sprintf(
1016
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
1017
                    rf.releases_id AS fileid, rel.id AS releases_id
1018
                FROM releases rel
1019
                INNER JOIN release_files rf ON rf.releases_id = rel.id
1020
                WHERE predb_id = 0'
1021
            );
1022
            $cats = 2;
1023
        } else {
1024
            $query = sprintf(
1025
                'SELECT rf.name AS textstring, rel.categories_id, rel.name, rel.searchname, rel.fromname, rel.groups_id,
1026
                    rf.releases_id AS fileid, rel.id AS releases_id
1027
                FROM releases rel
1028
                INNER JOIN release_files rf ON rf.releases_id = rel.id
1029
                WHERE (rel.isrenamed = %d OR rel.categories_id IN (%d, %d))
1030
                AND rel.predb_id = 0
1031
                AND rf.name LIKE %s',
1032
                self::IS_RENAMED_NONE,
1033
                Category::OTHER_MISC,
1034
                Category::OTHER_HASHED,
1035
                escapeString('%SDPORN%')
1036
            );
1037
        }
1038
1039
        $releases = $this->getReleases($time, $cats, $query);
1040
        $total = $releases ? $releases->count() : 0;
1041
1042
        if ($total > 0) {
1043
            $this->_totalReleases = $total;
1044
            $this->colorCLI->info(number_format($total) . ' xxx file names to process.');
1045
1046
            foreach ($releases as $release) {
1047
                $this->updateService->reset();
1048
                $this->xxxNameCheck($release, $echo, $type, $nameStatus, $show);
1049
                $this->updateService->incrementChecked();
1050
                $this->echoRenamed($show);
1051
            }
1052
            $this->echoFoundCount($echo, ' files');
1053
        } else {
1054
            $this->colorCLI->info('Nothing to fix.');
1055
        }
1056
    }
1057
1058
    /**
1059
     * Fix release names using mediainfo movie_name.
1060
     */
1061
    public function fixNamesWithMediaMovieName(int $time, bool $echo, int $cats, bool $nameStatus, bool $show): void
1062
    {
1063
        $type = 'Mediainfo, ';
1064
        $this->echoStartMessage($time, 'Mediainfo movie_name');
1065
1066
        if ($cats === 3) {
1067
            $query = sprintf(
1068
                'SELECT rel.id AS releases_id, rel.name, rel.name AS textstring, rel.predb_id, rel.searchname, rel.fromname, rel.groups_id, rel.categories_id, rel.id AS releases_id, rf.movie_name as movie_name
1069
                FROM releases rel
1070
                INNER JOIN media_infos rf ON rf.releases_id = rel.id
1071
                WHERE rel.predb_id = 0'
1072
            );
1073
            $cats = 2;
1074
        } else {
1075
            $query = sprintf(
1076
                'SELECT rel.id AS releases_id, rel.name, rel.name AS textstring, rel.predb_id, rel.searchname, rel.fromname, rel.groups_id, rel.categories_id, rel.id AS releases_id, rf.movie_name as movie_name, rf.file_name as file_name
1077
                FROM releases rel
1078
                INNER JOIN media_infos rf ON rf.releases_id = rel.id
1079
                WHERE rel.isrenamed = %d
1080
                AND rel.predb_id = 0',
1081
                self::IS_RENAMED_NONE
1082
            );
1083
            if ($cats === 2) {
1084
                $query .= PHP_EOL . 'AND rel.categories_id IN (' . Category::OTHER_MISC . ',' . Category::OTHER_HASHED . ')';
1085
            }
1086
        }
1087
1088
        $releases = $this->getReleases($time, $cats, $query);
1089
        $total = $releases ? $releases->count() : 0;
1090
1091
        if ($total > 0) {
1092
            $this->_totalReleases = $total;
1093
            $this->colorCLI->info(number_format($total) . ' mediainfo movie names to process.');
1094
1095
            foreach ($releases as $rel) {
1096
                $this->updateService->incrementChecked();
1097
                $this->updateService->reset();
1098
                $this->mediaMovieNameCheck($rel, $echo, $type, $nameStatus, $show);
1099
                $this->echoRenamed($show);
1100
            }
1101
            $this->echoFoundCount($echo, ' MediaInfo\'s');
1102
        } else {
1103
            $this->colorCLI->info('Nothing to fix.');
1104
        }
1105
    }
1106
1107
    /**
1108
     * Check for XXX release name.
1109
     */
1110
    protected function xxxNameCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
1111
    {
1112
        if (preg_match('/^.+?SDPORN/i', $release->textstring, $hit)) {
1113
            $this->updateService->updateRelease($release, $hit[0], 'fileCheck: XXX SDPORN', $echo, $type, $nameStatus, $show);
1114
            return true;
1115
        }
1116
1117
        $this->updateService->updateSingleColumn('proc_files', self::PROC_FILES_DONE, $release->releases_id);
1118
        return false;
1119
    }
1120
1121
    /**
1122
     * Check mediainfo movie_name for release name.
1123
     */
1124
    protected function mediaMovieNameCheck(object $release, bool $echo, string $type, bool $nameStatus, bool $show): bool
1125
    {
1126
        $newName = '';
1127
1128
        if (!empty($release->movie_name)) {
1129
            if (preg_match(ReleaseUpdateService::PREDB_REGEX, $release->movie_name, $hit)) {
1130
                $newName = $hit[1];
1131
            } elseif (preg_match('/(.+),(\sRMZ\.cr)?$/i', $release->movie_name, $hit)) {
1132
                $newName = $hit[1];
1133
            } else {
1134
                $newName = $release->movie_name;
1135
            }
1136
        }
1137
1138
        if ($newName !== '') {
1139
            $this->updateService->updateRelease($release, $newName, 'MediaInfo: Movie Name', $echo, $type, $nameStatus, $show, $release->predb_id ?? 0);
1140
            return true;
1141
        }
1142
1143
        $this->updateService->updateSingleColumn('proc_uid', self::PROC_UID_DONE, $release->releases_id);
1144
        return false;
1145
    }
1146
1147
    /**
1148
     * Check the array using regex for a clean name.
1149
     *
1150
     * @throws \Exception
1151
     */
1152
    public function checkName(object $release, bool $echo, string $type, bool $nameStatus, bool $show, bool $preId = false): bool
1153
    {
1154
        // Check PreDB first
1155
        $preDbMatch = $this->updateService->checkPreDbMatch($release, $release->textstring);
1156
        if ($preDbMatch !== null) {
1157
            $this->updateService->updateRelease($release, $preDbMatch['title'], 'preDB: Match', $echo, $type, $nameStatus, $show, $preDbMatch['id']);
1158
            return true;
1159
        }
1160
1161
        if ($preId) {
1162
            return $this->updateService->matched;
1163
        }
1164
1165
        // Route to appropriate checker based on type
1166
        switch ($type) {
1167
            case 'PAR2, ':
1168
                $result = $this->fileExtractor->extractFromFile($release->textstring);
1169
                if ($result !== null) {
1170
                    $this->updateService->updateRelease($release, $result->newName, 'fileCheck: ' . $result->method, $echo, $type, $nameStatus, $show);
1171
                }
1172
                break;
1173
1174
            case 'NFO, ':
1175
                $result = $this->nfoExtractor->extractFromNfo($release->textstring);
1176
                if ($result !== null) {
1177
                    $this->updateService->updateRelease($release, $result->newName, 'nfoCheck: ' . $result->method, $echo, $type, $nameStatus, $show);
1178
                }
1179
                break;
1180
1181
            case 'Filenames, ':
1182
                // Try PreDB file check
1183
                if (!$this->updateService->matched) {
1184
                    $this->preDbFileCheck($release, $echo, $type, $nameStatus, $show);
1185
                }
1186
                // Try PreDB title check
1187
                if (!$this->updateService->matched) {
1188
                    $this->preDbTitleCheck($release, $echo, $type, $nameStatus, $show);
1189
                }
1190
                // Try file name extraction
1191
                if (!$this->updateService->matched) {
1192
                    $result = $this->fileExtractor->extractFromFile($release->textstring);
1193
                    if ($result !== null) {
1194
                        $this->updateService->updateRelease($release, $result->newName, 'fileCheck: ' . $result->method, $echo, $type, $nameStatus, $show);
1195
                    }
1196
                }
1197
                break;
1198
1199
            default:
1200
                // Use pattern checker service
1201
                $result = $this->checkerService->check($release, $release->textstring);
1202
                if ($result !== null) {
1203
                    $this->updateService->updateRelease($release, $result->newName, $result->getFormattedMethod(), $echo, $type, $nameStatus, $show);
1204
                }
1205
        }
1206
1207
        // Update processing flags if not matched
1208
        if ($nameStatus === true && !$this->updateService->matched) {
1209
            $this->updateProcessingFlags($type, $release->releases_id);
1210
        }
1211
1212
        return $this->updateService->matched;
1213
    }
1214
1215
    /**
1216
     * Update processing flags based on type.
1217
     */
1218
    protected function updateProcessingFlags(string $type, int $releaseId): void
1219
    {
1220
        switch ($type) {
1221
            case 'NFO, ':
1222
                $this->updateService->updateSingleColumn('proc_nfo', self::PROC_NFO_DONE, $releaseId);
1223
                break;
1224
            case 'Filenames, ':
1225
                $this->updateService->updateSingleColumn('proc_files', self::PROC_FILES_DONE, $releaseId);
1226
                break;
1227
            case 'PAR2, ':
1228
                $this->updateService->updateSingleColumn('proc_par2', self::PROC_PAR2_DONE, $releaseId);
1229
                break;
1230
            case 'PAR2 hash, ':
1231
                $this->updateService->updateSingleColumn('proc_hash16k', self::PROC_HASH16K_DONE, $releaseId);
1232
                break;
1233
            case 'SRR, ':
1234
                $this->updateService->updateSingleColumn('proc_srr', self::PROC_SRR_DONE, $releaseId);
1235
                break;
1236
            case 'UID, ':
1237
            case 'Mediainfo, ':
1238
                $this->updateService->updateSingleColumn('proc_uid', self::PROC_UID_DONE, $releaseId);
1239
                break;
1240
            case 'CRC32, ':
1241
                $this->updateService->updateSingleColumn('proc_crc32', self::PROC_CRC_DONE, $releaseId);
1242
                break;
1243
        }
1244
    }
1245
1246
    /**
1247
     * Match a release filename to a PreDB filename or title.
1248
     *
1249
     * @throws \Exception
1250
     */
1251
    public function matchPreDbFiles(object $release, bool $echo, bool $nameStatus, bool $show): int
1252
    {
1253
        $matching = 0;
1254
1255
        $files = explode('||', $release->filename ?? '');
1256
        $prioritizedFiles = $this->filePrioritizer->prioritizeForPreDb($files);
1257
1258
        foreach ($prioritizedFiles as $fileName) {
1259
            $cleanedFileName = $this->fileNameCleaner->cleanForMatching($fileName);
1260
1261
            if (empty($cleanedFileName) || strlen($cleanedFileName) < 8) {
1262
                continue;
1263
            }
1264
1265
            $preMatch = $this->preMatch($cleanedFileName);
1266
            if ($preMatch[0] === true) {
1267
                if (config('nntmux.elasticsearch_enabled') === true) {
1268
                    $results = $this->elasticsearch->searchPreDb($preMatch[1]);
1269
                } else {
1270
                    $results = Arr::get($this->manticore->searchIndexes('predb_rt', $preMatch[1], ['filename', 'title']), 'data');
1271
                }
1272
1273
                if (!empty($results)) {
1274
                    foreach ($results as $result) {
1275
                        if (!empty($result)) {
1276
                            $preFtMatch = $this->preMatch($result['filename'] ?? '');
1277
                            if ($preFtMatch[0] === true) {
1278
                                if ($result['title'] !== $release->searchname) {
1279
                                    $this->updateService->updateRelease($release, $result['title'], 'file matched source: ' . $result['source'], $echo, 'PreDB file match, ', $nameStatus, $show);
1280
                                } else {
1281
                                    $this->updateService->updateSingleColumn('predb_id', $result['id'], $release->releases_id);
1282
                                }
1283
                                $matching++;
1284
                                return $matching;
1285
                            }
1286
                        }
1287
                    }
1288
                }
1289
            }
1290
        }
1291
1292
        return $matching;
1293
    }
1294
1295
    /**
1296
     * Pre-match check for filename patterns.
1297
     */
1298
    protected function preMatch(string $fileName): array
1299
    {
1300
        $result = preg_match('/(\d{2}\.\d{2}\.\d{2})+([\w\-.]+[\w]$)/i', $fileName, $hit);
1301
        return [$result === 1, $hit[0] ?? ''];
1302
    }
1303
1304
    /**
1305
     * Check if a release name looks like a season pack.
1306
     */
1307
    public function isSeasonPack(string $name): bool
1308
    {
1309
        // Season pack pattern: S01 without E01
1310
        return (bool) preg_match('/S\d{1,2}(?!E\d)/i', $name);
1311
    }
1312
1313
    /**
1314
     * Reset the update service state.
1315
     */
1316
    public function reset(): void
1317
    {
1318
        $this->updateService->reset();
1319
    }
1320
1321
    /**
1322
     * Retrieves releases and their file names to attempt PreDB matches.
1323
     *
1324
     * @throws \Exception
1325
     */
1326
    public function getPreFileNames(array $args = []): void
1327
    {
1328
        $show = isset($args[2]) && $args[2] === 'show';
1329
1330
        if (isset($args[1]) && is_numeric($args[1])) {
1331
            $limit = 'LIMIT ' . $args[1];
1332
            $orderBy = 'ORDER BY r.id DESC';
1333
        } else {
1334
            $orderBy = 'ORDER BY r.id ASC';
1335
            $limit = 'LIMIT 1000000';
1336
        }
1337
1338
        $this->colorCLI->info(PHP_EOL . 'Match PreFiles ' . ($args[1] ?? 'all') . ' Started at ' . now());
1339
        $this->colorCLI->info('Matching predb filename to cleaned release_files.name.');
1340
1341
        $counter = $counted = 0;
1342
        $timeStart = now();
1343
1344
        $query = Release::fromQuery(
1345
            sprintf(
1346
                "SELECT r.id AS releases_id, r.name, r.searchname,
1347
                    r.fromname, r.groups_id, r.categories_id,
1348
                    GROUP_CONCAT(rf.name ORDER BY LENGTH(rf.name) DESC SEPARATOR '||') AS filename
1349
                FROM releases r
1350
                INNER JOIN release_files rf ON r.id = rf.releases_id
1351
                WHERE rf.name IS NOT NULL
1352
                AND r.predb_id = 0
1353
                AND r.categories_id IN (%s)
1354
                AND r.isrenamed = 0
1355
                GROUP BY r.id
1356
                %s %s",
1357
                implode(',', Category::OTHERS_GROUP),
1358
                $orderBy,
1359
                $limit
1360
            )
1361
        );
1362
1363
        if ($query->isNotEmpty()) {
1364
            $total = $query->count();
1365
1366
            if ($total > 0) {
1367
                $this->colorCLI->info(PHP_EOL . number_format($total) . ' releases to process.');
1368
1369
                foreach ($query as $row) {
1370
                    $success = $this->matchPreDbFiles($row, true, true, $show);
1371
                    if ($success === 1) {
1372
                        $counted++;
1373
                    }
1374
                    if ($show === false) {
1375
                        $this->colorCLI->info('Renamed Releases: [' . number_format($counted) . '] ' . (new ColorCLI())->percentString(++$counter, $total));
1376
                    }
1377
                }
1378
                $this->colorCLI->info(PHP_EOL . 'Renamed ' . number_format($counted) . ' releases in ' . now()->diffInSeconds($timeStart, true) . ' seconds.');
1379
            } else {
1380
                $this->colorCLI->info('Nothing to do.');
1381
            }
1382
        }
1383
    }
1384
}
1385