Issues (867)

app/Console/Commands/ReleasesFixNamesGroup.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Console\Commands;
6
7
use App\Models\Category;
8
use App\Models\Predb;
9
use App\Models\Release;
10
use App\Services\NameFixing\NameFixingService;
11
use App\Services\PostProcessService;
12
use Blacklight\Nfo;
13
use Blacklight\NNTP;
14
use Blacklight\NZBContents;
15
use Illuminate\Console\Command;
16
use Illuminate\Support\Facades\DB;
17
18
class ReleasesFixNamesGroup extends Command
19
{
20
    /**
21
     * The name and signature of the console command.
22
     *
23
     * @var string
24
     */
25
    protected $signature = 'releases:fix-names-group
26
                            {type : Type of fix (standard|predbft)}
27
                            {--guid-char= : GUID character to process (for standard type)}
28
                            {--limit=1000 : Maximum releases to process}
29
                            {--thread=1 : Thread number (for predbft type)}';
30
31
    /**
32
     * The console command description.
33
     *
34
     * @var string
35
     */
36
    protected $description = 'Fix release names using various methods (group-based processing)';
37
38
    private NameFixingService $nameFixingService;
39
40
    private int $checked = 0;
41
42
    /**
43
     * Execute the console command.
44
     */
45
    public function handle(): int
46
    {
47
        $type = $this->argument('type');
48
        $maxPerRun = (int) $this->option('limit');
49
50
        $this->nameFixingService = new NameFixingService;
51
52
        switch ($type) {
53
            case 'standard':
54
                return $this->processStandard($maxPerRun);
55
56
            case 'predbft':
57
                return $this->processPredbFulltext($maxPerRun);
58
59
            default:
60
                $this->error("Invalid type: {$type}. Use 'standard' or 'predbft'");
61
62
                return Command::FAILURE;
63
        }
64
    }
65
66
    /**
67
     * Process standard name fixing
68
     */
69
    protected function processStandard(int $maxPerRun): int
70
    {
71
        $guidChar = $this->option('guid-char');
72
73
        if ($guidChar === null) {
0 ignored issues
show
The condition $guidChar === null is always false.
Loading history...
74
            $this->error('--guid-char is required for standard type');
75
76
            return Command::FAILURE;
77
        }
78
79
        $this->info("Processing releases with GUID starting with: {$guidChar}");
80
        $this->info("Maximum per run: {$maxPerRun}");
81
82
        // Allow for larger filename return sets
83
        DB::statement('SET SESSION group_concat_max_len = 65536');
84
85
        // Find releases to process
86
        $releases = $this->fetchReleases($guidChar, $maxPerRun);
87
88
        if ($releases->isEmpty()) {
89
            $this->info('No releases to process');
90
91
            return Command::SUCCESS;
92
        }
93
94
        $this->info("Found {$releases->count()} releases to process");
95
        $bar = $this->output->createProgressBar($releases->count());
96
        $bar->start();
97
98
        $nntp = null;
0 ignored issues
show
The assignment to $nntp is dead and can be removed.
Loading history...
99
        $nzbcontents = null;
100
101
        foreach ($releases as $release) {
102
            $this->checked++;
103
            $this->nameFixingService->reset();
104
105
            // Process UID
106
            if ((int) $release->proc_uid === NameFixingService::PROC_UID_NONE &&
107
                (! empty($release->uid) || ! empty($release->mediainfo))) {
108
109
                if (! empty($release->uid)) {
110
                    $this->nameFixingService->checkName($release, true, 'UID, ', true, true);
111
                }
112
113
                if (!$this->nameFixingService->getUpdateService()->matched && ! empty($release->mediainfo)) {
114
                    $this->nameFixingService->checkName($release, true, 'Mediainfo, ', true, true);
115
                }
116
            }
117
118
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_uid', NameFixingService::PROC_UID_DONE, $release->releases_id);
119
120
            if ($this->nameFixingService->getUpdateService()->matched) {
121
                $bar->advance();
122
123
                continue;
124
            }
125
126
            // Process CRC32
127
            if ((int) $release->proc_crc32 === NameFixingService::PROC_CRC_NONE && ! empty($release->crc)) {
128
                $this->nameFixingService->reset();
129
                $this->nameFixingService->checkName($release, true, 'CRC32, ', true, true);
130
            }
131
132
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_crc32', NameFixingService::PROC_CRC_DONE, $release->releases_id);
133
134
            if ($this->nameFixingService->getUpdateService()->matched) {
135
                $bar->advance();
136
137
                continue;
138
            }
139
140
            // Process SRR
141
            if ((int) $release->proc_srr === NameFixingService::PROC_SRR_NONE) {
142
                $this->nameFixingService->reset();
143
                $this->nameFixingService->checkName($release, true, 'SRR, ', true, true);
144
            }
145
146
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_srr', NameFixingService::PROC_SRR_DONE, $release->releases_id);
147
148
            if ($this->nameFixingService->getUpdateService()->matched) {
149
                $bar->advance();
150
151
                continue;
152
            }
153
154
            // Process PAR2 hash
155
            if ((int) $release->proc_hash16k === NameFixingService::PROC_HASH16K_NONE && ! empty($release->hash)) {
156
                $this->nameFixingService->reset();
157
                $this->nameFixingService->checkName($release, true, 'PAR2 hash, ', true, true);
158
            }
159
160
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_hash16k', NameFixingService::PROC_HASH16K_DONE, $release->releases_id);
161
162
            if ($this->nameFixingService->getUpdateService()->matched) {
163
                $bar->advance();
164
165
                continue;
166
            }
167
168
            // Process NFO
169
            if ((int) $release->nfostatus === Nfo::NFO_FOUND &&
170
                (int) $release->proc_nfo === NameFixingService::PROC_NFO_NONE &&
171
                ! empty($release->textstring) &&
172
                ! preg_match('/^=newz\[NZB\]=\w+/', $release->textstring)) {
173
174
                $this->nameFixingService->reset();
175
                $this->nameFixingService->checkName($release, true, 'NFO, ', true, true);
176
            }
177
178
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_nfo', NameFixingService::PROC_NFO_DONE, $release->releases_id);
179
180
            if ($this->nameFixingService->getUpdateService()->matched) {
181
                $bar->advance();
182
183
                continue;
184
            }
185
186
            // Process filenames
187
            if ((int) $release->fileid > 0 && (int) $release->proc_files === NameFixingService::PROC_FILES_NONE) {
188
                $this->nameFixingService->reset();
189
                $fileNames = explode('|', $release->filestring);
190
191
                if (is_array($fileNames)) {
192
                    $releaseFile = $release;
193
                    foreach ($fileNames as $fileName) {
194
                        if (!$this->nameFixingService->getUpdateService()->matched) {
195
                            $releaseFile->textstring = $fileName;
196
                            $this->nameFixingService->checkName($releaseFile, true, 'Filenames, ', true, true);
197
                        }
198
                    }
199
                }
200
            }
201
202
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_files', NameFixingService::PROC_FILES_DONE, $release->releases_id);
203
204
            if ($this->nameFixingService->getUpdateService()->matched) {
205
                $bar->advance();
206
207
                continue;
208
            }
209
210
            // Process PAR2
211
            if ((int) $release->proc_par2 === NameFixingService::PROC_PAR2_NONE) {
212
                // Initialize NZB contents if needed
213
                if (! isset($nzbcontents)) {
214
                    $nntp = new NNTP();
215
                    $compressedHeaders = config('nntmux_nntp.compressed_headers');
216
217
                    if ((config('nntmux_nntp.use_alternate_nntp_server') === true
218
                        ? $nntp->doConnect($compressedHeaders, true)
219
                        : $nntp->doConnect()) !== true) {
220
                        $this->warn('Unable to connect to usenet for PAR2 processing');
221
                    } else {
222
                        $Nfo = new Nfo();
223
                        $nzbcontents = new NZBContents([
0 ignored issues
show
The call to Blacklight\NZBContents::__construct() has too many arguments starting with array('Echo' => false, '...ProcessService::class)). ( Ignorable by Annotation )

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

223
                        $nzbcontents = /** @scrutinizer ignore-call */ new NZBContents([

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
224
                            'Echo' => false,
225
                            'NNTP' => $nntp,
226
                            'Nfo' => $Nfo,
227
                            'PostProcess' => app(PostProcessService::class),
228
                        ]);
229
                    }
230
                }
231
232
                if (isset($nzbcontents)) {
233
                    $nzbcontents->checkPAR2($release->guid, $release->releases_id, $release->groups_id, 1, 1);
234
                }
235
            }
236
237
            $this->nameFixingService->getUpdateService()->updateSingleColumn('proc_par2', NameFixingService::PROC_PAR2_DONE, $release->releases_id);
238
239
            $bar->advance();
240
        }
241
242
        $bar->finish();
243
        $this->newLine(2);
244
245
        $this->info("✅ Processed {$this->checked} releases");
246
        $this->info("✅ Fixed {$this->nameFixingService->getUpdateService()->fixed} release names");
247
248
        return Command::SUCCESS;
249
    }
250
251
    /**
252
     * Process PreDB fulltext matching
253
     */
254
    protected function processPredbFulltext(int $maxPerRun): int
255
    {
256
        $thread = (int) $this->option('thread');
257
        $offset = $thread * $maxPerRun - $maxPerRun;
258
259
        $this->info('Processing PreDB fulltext matching');
260
        $this->info("Thread: {$thread}, Limit: {$maxPerRun}, Offset: {$offset}");
261
262
        $pres = Predb::fromQuery(
263
            sprintf(
264
                '
265
                SELECT p.id AS predb_id, p.title, p.source, p.searched
266
                FROM predb p
267
                WHERE LENGTH(p.title) >= 15 AND p.title NOT REGEXP "[\"\<\> ]"
268
                AND p.searched = 0
269
                AND p.predate < (NOW() - INTERVAL 1 DAY)
270
                ORDER BY p.predate ASC
271
                LIMIT %s
272
                OFFSET %s',
273
                $maxPerRun,
274
                $offset
275
            )
276
        );
277
278
        if ($pres->isEmpty()) {
279
            $this->info('No PreDB entries to process');
280
281
            return Command::SUCCESS;
282
        }
283
284
        $this->info("Found {$pres->count()} PreDB entries to process");
285
        $bar = $this->output->createProgressBar($pres->count());
286
        $bar->start();
287
288
        foreach ($pres as $pre) {
289
            $searched = 0;
290
291
            $ftmatched = $this->matchPredbFT($pre);
292
293
            if ($ftmatched > 0) {
294
                $searched = 1;
295
            } elseif ($ftmatched < 0) {
296
                $searched = -6;
297
            } else {
298
                $searched = $pre['searched'] - 1;
299
            }
300
301
            Predb::query()->where('id', $pre['predb_id'])->update(['searched' => $searched]);
302
            $this->checked++;
303
304
            $bar->advance();
305
        }
306
307
        $bar->finish();
308
        $this->newLine(2);
309
310
        $this->info("✅ Processed {$this->checked} PreDB entries");
311
312
        return Command::SUCCESS;
313
    }
314
315
    /**
316
     * Match a PreDB title to releases using full-text search.
317
     */
318
    protected function matchPredbFT(object $pre): int
0 ignored issues
show
The parameter $pre 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

318
    protected function matchPredbFT(/** @scrutinizer ignore-unused */ object $pre): int

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...
319
    {
320
        // This is a simplified version - the full implementation would use Manticore/Elasticsearch
321
        return 0;
322
    }
323
324
    /**
325
     * Fetch releases for processing
326
     */
327
    protected function fetchReleases(string $guidChar, int $maxPerRun)
328
    {
329
        return Release::fromQuery(sprintf("
330
            SELECT
331
                r.id AS releases_id, r.fromname, r.guid, r.groups_id, r.categories_id, r.name, r.searchname, r.proc_nfo,
332
                r.proc_uid, r.proc_files, r.proc_par2, r.ishashed, r.dehashstatus, r.nfostatus,
333
                r.size AS relsize, r.predb_id, r.proc_hash16k, r.proc_srr, r.proc_crc32,
334
                IFNULL(rf.releases_id, 0) AS fileid, IF(rf.ishashed = 1, rf.name, 0) AS filehash,
335
                IFNULL(GROUP_CONCAT(rf.name ORDER BY rf.name ASC SEPARATOR '|'), '') AS filestring,
336
                IFNULL(UNCOMPRESS(rn.nfo), '') AS textstring,
337
                IFNULL(ru.uniqueid, '') AS uid,
338
                IFNULL(ph.hash, 0) AS hash,
339
                IFNULL(rf.crc32, '') AS crc
340
            FROM releases r
341
            LEFT JOIN release_nfos rn ON rn.releases_id = r.id
342
            LEFT JOIN release_files rf ON rf.releases_id = r.id
343
            LEFT JOIN release_unique ru ON ru.releases_id = r.id
344
            LEFT JOIN par_hashes ph ON ph.releases_id = r.id
345
            WHERE r.leftguid = %s
346
            AND r.isrenamed = %d
347
            AND r.predb_id = 0
348
            AND r.passwordstatus >= 0
349
            AND r.nfostatus > %d
350
            AND (
351
                (r.nfostatus = %d AND r.proc_nfo = %d)
352
                OR r.proc_files = %d
353
                OR r.proc_uid = %d
354
                OR r.proc_par2 = %d
355
                OR r.proc_srr = %d
356
                OR r.proc_hash16k = %d
357
                OR r.proc_crc32 = %d
358
                OR (r.ishashed = 1 AND r.dehashstatus BETWEEN -6 AND 0)
359
            )
360
            AND r.categories_id IN (%s)
361
            GROUP BY r.id
362
            ORDER BY r.id DESC
363
            LIMIT %s",
364
            escapeString($guidChar),
365
            NameFixingService::IS_RENAMED_NONE,
366
            Nfo::NFO_UNPROC,
367
            Nfo::NFO_FOUND,
368
            NameFixingService::PROC_NFO_NONE,
369
            NameFixingService::PROC_FILES_NONE,
370
            NameFixingService::PROC_UID_NONE,
371
            NameFixingService::PROC_PAR2_NONE,
372
            NameFixingService::PROC_SRR_NONE,
373
            NameFixingService::PROC_HASH16K_NONE,
374
            NameFixingService::PROC_CRC_NONE,
375
            Category::getCategoryOthersGroup(),
376
            $maxPerRun
377
        ));
378
    }
379
}
380