Passed
Push — master ( d95312...98093b )
by Darko
15:58 queued 04:19
created

ReprocessUnmatchedTvReleases::handle()   D

Complexity

Conditions 17
Paths 11

Size

Total Lines 137
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 94
c 1
b 0
f 0
dl 0
loc 137
rs 4.3042
cc 17
nc 11
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Console\Commands;
4
5
use App\Models\Category;
6
use App\Models\Release;
7
use App\Services\TvProcessing\TvProcessingPipeline;
8
use Blacklight\ColorCLI;
9
use Illuminate\Console\Command;
10
use Illuminate\Support\Facades\Log;
11
12
class ReprocessUnmatchedTvReleases extends Command
13
{
14
    /**
15
     * The name and signature of the console command.
16
     *
17
     * @var string
18
     */
19
    protected $signature = 'tv:reprocess-unmatched
20
                            {--limit=0 : Limit the number of releases to process (0 = no limit)}
21
                            {--dry-run : Show how many releases would be processed without making changes}
22
                            {--debug : Show detailed debug information for each release}
23
                            {--sleep=0 : Sleep time in milliseconds between processing each release}';
24
25
    /**
26
     * The console command description.
27
     *
28
     * @var string
29
     */
30
    protected $description = 'Reprocess all TV releases that are not matched to any show in the database (videos_id = 0)';
31
32
    protected ColorCLI $colorCli;
33
34
    public function __construct()
35
    {
36
        parent::__construct();
37
        $this->colorCli = new ColorCLI();
38
    }
39
40
    /**
41
     * Execute the console command.
42
     */
43
    public function handle(): int
44
    {
45
        $limit = (int) $this->option('limit');
46
        $dryRun = (bool) $this->option('dry-run');
47
        $debug = (bool) $this->option('debug');
48
        $sleep = (int) $this->option('sleep');
49
50
        // Build query for unmatched TV releases
51
        $query = Release::query()
52
            ->select(['id', 'guid', 'searchname', 'videos_id', 'tv_episodes_id', 'categories_id'])
53
            ->where('videos_id', 0)
54
            ->whereBetween('categories_id', [Category::TV_ROOT, Category::TV_OTHER])
55
            ->orderBy('id', 'desc');
0 ignored issues
show
Bug introduced by
'id' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

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

55
            ->orderBy(/** @scrutinizer ignore-type */ 'id', 'desc');
Loading history...
56
57
        $totalCount = (clone $query)->count();
58
59
        if ($totalCount === 0) {
60
            $this->info('No unmatched TV releases found.');
61
            return self::SUCCESS;
62
        }
63
64
        $this->info("Found {$totalCount} unmatched TV release(s).");
65
66
        if ($limit > 0) {
67
            $query->limit($limit);
68
            $processCount = min($limit, $totalCount);
69
            $this->info("Processing limited to {$processCount} release(s).");
70
        } else {
71
            $processCount = $totalCount;
72
        }
73
74
        if ($dryRun) {
75
            $this->line("[Dry Run] Would process {$processCount} unmatched TV release(s).");
76
77
            // Show sample of releases that would be processed
78
            $sample = (clone $query)->limit(10)->get();
79
            if ($sample->isNotEmpty()) {
80
                $this->newLine();
81
                $this->info('Sample of releases to be processed:');
82
                $rows = $sample->map(fn ($release) => [
83
                    $release->id,
84
                    $release->guid,
85
                    mb_substr($release->searchname, 0, 60) . (strlen($release->searchname) > 60 ? '...' : ''),
86
                    $release->categories_id,
87
                ])->toArray();
88
                $this->table(['ID', 'GUID', 'Search Name', 'Category'], $rows);
89
            }
90
91
            return self::SUCCESS;
92
        }
93
94
        $this->info("Starting to process {$processCount} unmatched TV release(s)...");
95
        $this->newLine();
96
97
        $bar = $this->output->createProgressBar($processCount);
98
        $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% | Matched: %matched% | Failed: %failed% | Elapsed: %elapsed:6s%');
99
        $bar->setMessage('0', 'matched');
100
        $bar->setMessage('0', 'failed');
101
        $bar->start();
102
103
        $matched = 0;
104
        $failed = 0;
105
        $processed = 0;
106
107
        try {
108
            $pipeline = TvProcessingPipeline::createDefault(echoOutput: false);
109
110
            $query->chunkById(100, function ($releases) use ($pipeline, $debug, $sleep, $bar, &$matched, &$failed, &$processed, $limit) {
111
                foreach ($releases as $release) {
112
                    if ($limit > 0 && $processed >= $limit) {
113
                        return false; // Stop chunking
114
                    }
115
116
                    try {
117
                        $result = $pipeline->processRelease($release, $debug);
118
119
                        if ($result['matched']) {
120
                            $matched++;
121
                            if ($debug) {
122
                                $this->newLine();
123
                                $this->colorCli->primary("Matched: {$release->searchname}");
124
                                $this->info('  Provider: ' . ($result['provider'] ?? 'Unknown'));
125
                                $this->info('  Video ID: ' . ($result['video_id'] ?? 'N/A'));
126
                                $this->info('  Episode ID: ' . ($result['episode_id'] ?? 'N/A'));
127
                            }
128
                        } else {
129
                            $failed++;
130
                            if ($debug) {
131
                                $this->newLine();
132
                                $this->colorCli->warning("No match: {$release->searchname}");
133
                            }
134
                        }
135
                    } catch (\Throwable $e) {
136
                        $failed++;
137
                        Log::error("Error processing release {$release->guid}: " . $e->getMessage());
138
                        if ($debug) {
139
                            $this->newLine();
140
                            $this->error("Error processing {$release->searchname}: " . $e->getMessage());
141
                        }
142
                    }
143
144
                    $processed++;
145
                    $bar->setMessage((string) $matched, 'matched');
146
                    $bar->setMessage((string) $failed, 'failed');
147
                    $bar->advance();
148
149
                    if ($sleep > 0) {
150
                        usleep($sleep * 1000);
151
                    }
152
                }
153
154
                return true;
155
            });
156
157
        } catch (\Throwable $e) {
158
            $bar->finish();
159
            $this->newLine();
160
            $this->error('Fatal error during processing: ' . $e->getMessage());
161
            Log::error('Fatal error in tv:reprocess-unmatched: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
162
            return self::FAILURE;
163
        }
164
165
        $bar->finish();
166
        $this->newLine(2);
167
168
        $this->info('Processing complete!');
169
        $this->table(
170
            ['Total Processed', 'Matched', 'Not Matched', 'Match Rate'],
171
            [[
172
                number_format($processed),
173
                number_format($matched),
174
                number_format($failed),
175
                $processed > 0 ? round(($matched / $processed) * 100, 2) . '%' : '0%',
176
            ]]
177
        );
178
179
        return self::SUCCESS;
180
    }
181
}
182
183