NameFixingService::echoFoundCount()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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