Passed
Push — master ( ae3f18...b8bba4 )
by Darko
10:59
created

CollectionHandler   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 19
eloc 113
dl 0
loc 248
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
B getOrCreateCollection() 0 63 7
A reset() 0 5 1
A getInsertedIds() 0 3 1
A insertCollectionSqlite() 0 34 2
A getAllIds() 0 3 1
A insertCollectionMysql() 0 44 3
A getBatchHashes() 0 3 1
A insertOrGetCollection() 0 38 2
A __construct() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Services\Binaries;
6
7
use App\Models\Collection;
8
use App\Services\XrefService;
9
use Blacklight\CollectionsCleaning;
0 ignored issues
show
Bug introduced by
The type Blacklight\CollectionsCleaning was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Illuminate\Support\Facades\DB;
11
use Illuminate\Support\Facades\Log;
12
13
/**
14
 * Handles collection record creation and retrieval during header storage.
15
 */
16
final class CollectionHandler
17
{
18
    private CollectionsCleaning $collectionsCleaning;
19
20
    private XrefService $xrefService;
21
22
    /** @var array<string, int> Cached collection IDs by key */
23
    private array $collectionIds = [];
24
25
    /** @var array<int, true> IDs of collections created in this batch */
26
    private array $insertedCollectionIds = [];
27
28
    /** @var array<string, true> Collection hashes touched in this batch */
29
    private array $batchCollectionHashes = [];
30
31
    public function __construct(
32
        ?CollectionsCleaning $collectionsCleaning = null,
33
        ?XrefService $xrefService = null
34
    ) {
35
        $this->collectionsCleaning = $collectionsCleaning ?? new CollectionsCleaning;
36
        $this->xrefService = $xrefService ?? new XrefService;
37
    }
38
39
    /**
40
     * Reset state for a new batch.
41
     */
42
    public function reset(): void
43
    {
44
        $this->collectionIds = [];
45
        $this->insertedCollectionIds = [];
46
        $this->batchCollectionHashes = [];
47
    }
48
49
    /**
50
     * Get or create a collection for the given header.
51
     *
52
     * @return int|null Collection ID or null on failure
53
     */
54
    public function getOrCreateCollection(
55
        array $header,
56
        int $groupId,
57
        string $groupName,
58
        int $totalFiles,
59
        string $batchNoise
60
    ): ?int {
61
        $collMatch = $this->collectionsCleaning->collectionsCleaner(
62
            $header['matches'][1],
63
            $groupName
64
        );
65
66
        $collectionKey = $collMatch['name'].$totalFiles;
67
68
        // Return cached ID if already processed this batch
69
        if (isset($this->collectionIds[$collectionKey])) {
70
            return $this->collectionIds[$collectionKey];
71
        }
72
73
        $collectionHash = sha1($collectionKey);
74
        $this->batchCollectionHashes[$collectionHash] = true;
75
76
        $headerDate = is_numeric($header['Date']) ? (int) $header['Date'] : strtotime($header['Date']);
77
        $now = now()->timestamp;
78
        $unixtime = min($headerDate, $now) ?: $now;
79
80
        $existingXref = Collection::whereCollectionhash($collectionHash)->value('xref');
81
        $headerTokens = $this->xrefService->extractTokens($header['Xref'] ?? '');
82
        $newTokens = $this->xrefService->diffNewTokens($existingXref, $header['Xref'] ?? '');
83
        $finalXrefAppend = implode(' ', $newTokens);
84
85
        $subject = substr(mb_convert_encoding($header['matches'][1], 'UTF-8', mb_list_encodings()), 0, 255);
0 ignored issues
show
Bug introduced by
It seems like mb_convert_encoding($hea...', mb_list_encodings()) can also be of type array; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

85
        $subject = substr(/** @scrutinizer ignore-type */ mb_convert_encoding($header['matches'][1], 'UTF-8', mb_list_encodings()), 0, 255);
Loading history...
86
        $fromName = mb_convert_encoding($header['From'], 'UTF-8', mb_list_encodings());
87
88
        $driver = DB::getDriverName();
89
90
        try {
91
            $collectionId = $this->insertOrGetCollection(
92
                $driver,
93
                $subject,
94
                $fromName,
0 ignored issues
show
Bug introduced by
It seems like $fromName can also be of type array; however, parameter $fromName of App\Services\Binaries\Co...insertOrGetCollection() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

94
                /** @scrutinizer ignore-type */ $fromName,
Loading history...
95
                $unixtime,
96
                $headerTokens,
97
                $finalXrefAppend,
98
                $groupId,
99
                $totalFiles,
100
                $collectionHash,
101
                $collMatch['id'],
102
                $batchNoise
103
            );
104
105
            if ($collectionId > 0) {
106
                $this->collectionIds[$collectionKey] = $collectionId;
107
108
                return $collectionId;
109
            }
110
        } catch (\Throwable $e) {
111
            if (config('app.debug') === true) {
112
                Log::error('Collection insert failed: '.$e->getMessage());
113
            }
114
        }
115
116
        return null;
117
    }
118
119
    private function insertOrGetCollection(
120
        string $driver,
121
        string $subject,
122
        string $fromName,
123
        int $unixtime,
124
        array $headerTokens,
125
        string $finalXrefAppend,
126
        int $groupId,
127
        int $totalFiles,
128
        string $collectionHash,
129
        int $regexId,
130
        string $batchNoise
131
    ): int {
132
        if ($driver === 'sqlite') {
133
            return $this->insertCollectionSqlite(
134
                $subject,
135
                $fromName,
136
                $unixtime,
137
                $headerTokens,
138
                $groupId,
139
                $totalFiles,
140
                $collectionHash,
141
                $regexId,
142
                $batchNoise
143
            );
144
        }
145
146
        return $this->insertCollectionMysql(
147
            $subject,
148
            $fromName,
149
            $unixtime,
150
            $headerTokens,
151
            $finalXrefAppend,
152
            $groupId,
153
            $totalFiles,
154
            $collectionHash,
155
            $regexId,
156
            $batchNoise
157
        );
158
    }
159
160
    private function insertCollectionSqlite(
161
        string $subject,
162
        string $fromName,
163
        int $unixtime,
164
        array $headerTokens,
165
        int $groupId,
166
        int $totalFiles,
167
        string $collectionHash,
168
        int $regexId,
169
        string $batchNoise
170
    ): int {
171
        DB::statement(
172
            'INSERT OR IGNORE INTO collections (subject, fromname, date, xref, groups_id, totalfiles, collectionhash, collection_regexes_id, dateadded, noise) VALUES (?, ?, datetime(?, "unixepoch"), ?, ?, ?, ?, datetime("now"), ?)',
173
            [
174
                $subject,
175
                $fromName,
176
                $unixtime,
177
                implode(' ', $headerTokens),
178
                $groupId,
179
                $totalFiles,
180
                $collectionHash,
181
                $regexId,
182
                $batchNoise,
183
            ]
184
        );
185
186
        $lastId = (int) DB::connection()->getPdo()->lastInsertId();
187
        if ($lastId > 0) {
188
            $this->insertedCollectionIds[$lastId] = true;
189
190
            return $lastId;
191
        }
192
193
        return (int) (Collection::whereCollectionhash($collectionHash)->value('id') ?? 0);
194
    }
195
196
    private function insertCollectionMysql(
197
        string $subject,
198
        string $fromName,
199
        int $unixtime,
200
        array $headerTokens,
201
        string $finalXrefAppend,
202
        int $groupId,
203
        int $totalFiles,
204
        string $collectionHash,
205
        int $regexId,
206
        string $batchNoise
207
    ): int {
208
        $insertSql = 'INSERT INTO collections '
209
            .'(subject, fromname, date, xref, groups_id, totalfiles, collectionhash, collection_regexes_id, dateadded, noise) '
210
            .'VALUES (?, ?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, NOW(), ?) '
211
            .'ON DUPLICATE KEY UPDATE dateadded = NOW()';
212
213
        $bindings = [
214
            $subject,
215
            $fromName,
216
            $unixtime,
217
            implode(' ', $headerTokens),
218
            $groupId,
219
            $totalFiles,
220
            $collectionHash,
221
            $regexId,
222
            $batchNoise,
223
        ];
224
225
        if ($finalXrefAppend !== '') {
226
            $insertSql .= ', xref = CONCAT(xref, "\\n", ?)';
227
            $bindings[] = $finalXrefAppend;
228
        }
229
230
        DB::statement($insertSql, $bindings);
231
232
        $lastId = (int) DB::connection()->getPdo()->lastInsertId();
233
        if ($lastId > 0) {
234
            $this->insertedCollectionIds[$lastId] = true;
235
236
            return $lastId;
237
        }
238
239
        return (int) (Collection::whereCollectionhash($collectionHash)->value('id') ?? 0);
240
    }
241
242
    /**
243
     * Get IDs created in this batch.
244
     */
245
    public function getInsertedIds(): array
246
    {
247
        return array_keys($this->insertedCollectionIds);
248
    }
249
250
    /**
251
     * Get all collection IDs processed this batch.
252
     */
253
    public function getAllIds(): array
254
    {
255
        return array_values(array_unique(array_map('intval', $this->collectionIds)));
256
    }
257
258
    /**
259
     * Get all collection hashes processed this batch.
260
     */
261
    public function getBatchHashes(): array
262
    {
263
        return array_keys($this->batchCollectionHashes);
264
    }
265
}
266
267