Total Complexity | 217 |
Total Lines | 1726 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Binaries often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Binaries, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class Binaries |
||
18 | { |
||
19 | public const OPTYPE_BLACKLIST = 1; |
||
20 | public const OPTYPE_WHITELIST = 2; |
||
21 | |||
22 | public const BLACKLIST_DISABLED = 0; |
||
23 | public const BLACKLIST_ENABLED = 1; |
||
24 | |||
25 | public const BLACKLIST_FIELD_SUBJECT = 1; |
||
26 | public const BLACKLIST_FIELD_FROM = 2; |
||
27 | public const BLACKLIST_FIELD_MESSAGEID = 3; |
||
28 | |||
29 | /** |
||
30 | * @var array |
||
31 | */ |
||
32 | public $blackList = []; |
||
33 | |||
34 | /** |
||
35 | * @var array |
||
36 | */ |
||
37 | public $whiteList = []; |
||
38 | |||
39 | /** |
||
40 | * @var int |
||
41 | */ |
||
42 | public $messageBuffer; |
||
43 | |||
44 | /** |
||
45 | * @var \Blacklight\ColorCLI |
||
46 | */ |
||
47 | protected $_colorCLI; |
||
48 | |||
49 | /** |
||
50 | * @var \Blacklight\CollectionsCleaning |
||
51 | */ |
||
52 | protected $_collectionsCleaning; |
||
53 | |||
54 | /** |
||
55 | * @var \Blacklight\NNTP |
||
56 | */ |
||
57 | protected $_nntp; |
||
58 | |||
59 | /** |
||
60 | * Should we use header compression? |
||
61 | * |
||
62 | * @var bool |
||
63 | */ |
||
64 | protected $_compressedHeaders; |
||
65 | |||
66 | /** |
||
67 | * Should we use part repair? |
||
68 | * |
||
69 | * @var bool |
||
70 | */ |
||
71 | protected $_partRepair; |
||
72 | |||
73 | /** |
||
74 | * @var \Blacklight\db\DB |
||
75 | */ |
||
76 | protected $_pdo; |
||
77 | |||
78 | /** |
||
79 | * How many days to go back on a new group? |
||
80 | * |
||
81 | * @var bool |
||
82 | */ |
||
83 | protected $_newGroupScanByDays; |
||
84 | |||
85 | /** |
||
86 | * How many headers to download on new groups? |
||
87 | * |
||
88 | * @var int |
||
89 | */ |
||
90 | protected $_newGroupMessagesToScan; |
||
91 | |||
92 | /** |
||
93 | * How many days to go back on new groups? |
||
94 | * |
||
95 | * @var int |
||
96 | */ |
||
97 | protected $_newGroupDaysToScan; |
||
98 | |||
99 | /** |
||
100 | * How many headers to download per run of part repair? |
||
101 | * |
||
102 | * @var int |
||
103 | */ |
||
104 | protected $_partRepairLimit; |
||
105 | |||
106 | /** |
||
107 | * Should we show dropped yEnc to CLI? |
||
108 | * |
||
109 | * @var bool |
||
110 | */ |
||
111 | protected $_showDroppedYEncParts; |
||
112 | |||
113 | /** |
||
114 | * Echo to cli? |
||
115 | * |
||
116 | * @var bool |
||
117 | */ |
||
118 | protected $_echoCLI; |
||
119 | |||
120 | /** |
||
121 | * @var bool |
||
122 | */ |
||
123 | protected $_debug = false; |
||
124 | |||
125 | /** |
||
126 | * Max tries to download headers. |
||
127 | * @var int |
||
128 | */ |
||
129 | protected $_partRepairMaxTries; |
||
130 | |||
131 | /** |
||
132 | * An array of binaryblacklist IDs that should have their activity date updated. |
||
133 | * @var array(int) |
||
134 | */ |
||
135 | protected $_binaryBlacklistIdsToUpdate = []; |
||
136 | |||
137 | /** |
||
138 | * @var float microseconds time of cleaning process start |
||
139 | */ |
||
140 | protected $startCleaning; |
||
141 | |||
142 | /** |
||
143 | * @var float microseconds time of the start of the scan function |
||
144 | */ |
||
145 | protected $startLoop; |
||
146 | |||
147 | /** |
||
148 | * @var bool Is this retrieved header a multigroup one? |
||
149 | */ |
||
150 | protected $multiGroup; |
||
151 | |||
152 | /** |
||
153 | * @var bool |
||
154 | */ |
||
155 | protected $allAsMgr; |
||
156 | |||
157 | /** |
||
158 | * @var string How long it took in seconds to download headers |
||
159 | */ |
||
160 | protected $timeHeaders; |
||
161 | |||
162 | /** |
||
163 | * @var string How long it took in seconds to clean/parse headers |
||
164 | */ |
||
165 | protected $timeCleaning; |
||
166 | |||
167 | /** |
||
168 | * @var float microseconds time part repair was started |
||
169 | */ |
||
170 | protected $startPR; |
||
171 | |||
172 | /** |
||
173 | * @var array The CBP/MGR tables names |
||
174 | */ |
||
175 | protected $tableNames; |
||
176 | |||
177 | /** |
||
178 | * @var float microseconds time header update was started |
||
179 | */ |
||
180 | protected $startUpdate; |
||
181 | |||
182 | /** |
||
183 | * @var string The time it took to insert the headers |
||
184 | */ |
||
185 | protected $timeInsert; |
||
186 | |||
187 | /** |
||
188 | * @var array the header currently being scanned |
||
189 | */ |
||
190 | protected $header; |
||
191 | |||
192 | /** |
||
193 | * @var bool Should we add parts to part repair queue? |
||
194 | */ |
||
195 | protected $addToPartRepair; |
||
196 | |||
197 | /** |
||
198 | * @var array Numbers of Headers received from the USP |
||
199 | */ |
||
200 | protected $headersReceived; |
||
201 | |||
202 | /** |
||
203 | * @var array The current newsgroup information being updated |
||
204 | */ |
||
205 | protected $groupMySQL; |
||
206 | |||
207 | /** |
||
208 | * @var int the last article number in the range |
||
209 | */ |
||
210 | protected $last; |
||
211 | |||
212 | /** |
||
213 | * @var int the first article number in the range |
||
214 | */ |
||
215 | protected $first; |
||
216 | |||
217 | /** |
||
218 | * @var int How many received headers were not yEnc encoded |
||
219 | */ |
||
220 | protected $notYEnc; |
||
221 | |||
222 | /** |
||
223 | * @var int How many received headers were blacklist matched |
||
224 | */ |
||
225 | protected $headersBlackListed; |
||
226 | |||
227 | /** |
||
228 | * @var array Header numbers that were not inserted |
||
229 | */ |
||
230 | protected $headersNotInserted; |
||
231 | |||
232 | /** |
||
233 | * Constructor. |
||
234 | * |
||
235 | * @param array $options Class instances / echo to CLI? |
||
236 | * |
||
237 | * @throws \Exception |
||
238 | */ |
||
239 | public function __construct(array $options = []) |
||
240 | { |
||
241 | $defaults = [ |
||
242 | 'Echo' => true, |
||
243 | 'CollectionsCleaning' => null, |
||
244 | 'ColorCLI' => null, |
||
245 | 'Logger' => null, |
||
246 | 'Groups' => null, |
||
247 | 'NNTP' => null, |
||
248 | 'Settings' => null, |
||
249 | ]; |
||
250 | $options += $defaults; |
||
251 | |||
252 | $this->_echoCLI = ($options['Echo'] && config('nntmux.echocli')); |
||
253 | |||
254 | $this->_pdo = ($options['Settings'] instanceof DB ? $options['Settings'] : new DB()); |
||
255 | $this->_colorCLI = ($options['ColorCLI'] instanceof ColorCLI ? $options['ColorCLI'] : new ColorCLI()); |
||
256 | $this->_nntp = ($options['NNTP'] instanceof NNTP ? $options['NNTP'] : new NNTP(['Echo' => $this->_colorCLI, 'Settings' => $this->_pdo, 'ColorCLI' => $this->_colorCLI])); |
||
257 | $this->_collectionsCleaning = ($options['CollectionsCleaning'] instanceof CollectionsCleaning ? $options['CollectionsCleaning'] : new CollectionsCleaning(['Settings' => $this->_pdo])); |
||
258 | |||
259 | $this->messageBuffer = Settings::settingValue('..maxmssgs') !== '' ? |
||
|
|||
260 | (int) Settings::settingValue('..maxmssgs') : 20000; |
||
261 | $this->_compressedHeaders = (int) Settings::settingValue('..compressedheaders') === 1; |
||
262 | $this->_partRepair = (int) Settings::settingValue('..partrepair') === 1; |
||
263 | $this->_newGroupScanByDays = (int) Settings::settingValue('..newgroupscanmethod') === 1; |
||
264 | $this->_newGroupMessagesToScan = Settings::settingValue('..newgroupmsgstoscan') !== '' ? (int) Settings::settingValue('..newgroupmsgstoscan') : 50000; |
||
265 | $this->_newGroupDaysToScan = Settings::settingValue('..newgroupdaystoscan') !== '' ? (int) Settings::settingValue('..newgroupdaystoscan') : 3; |
||
266 | $this->_partRepairLimit = Settings::settingValue('..maxpartrepair') !== '' ? (int) Settings::settingValue('..maxpartrepair') : 15000; |
||
267 | $this->_partRepairMaxTries = (Settings::settingValue('..partrepairmaxtries') !== '' ? (int) Settings::settingValue('..partrepairmaxtries') : 3); |
||
268 | $this->_showDroppedYEncParts = (int) Settings::settingValue('..showdroppedyencparts') === 1; |
||
269 | $this->allAsMgr = (int) Settings::settingValue('..allasmgr') === 1; |
||
270 | |||
271 | $this->blackList = $this->whiteList = []; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Download new headers for all active groups. |
||
276 | * |
||
277 | * @param int $maxHeaders (Optional) How many headers to download max. |
||
278 | * |
||
279 | * @return void |
||
280 | * @throws \Exception |
||
281 | */ |
||
282 | public function updateAllGroups($maxHeaders = 100000): void |
||
283 | { |
||
284 | $groups = Group::getActive(); |
||
285 | |||
286 | $groupCount = \count($groups); |
||
287 | if ($groupCount > 0) { |
||
288 | $counter = 1; |
||
289 | $allTime = microtime(true); |
||
290 | |||
291 | $this->log( |
||
292 | 'Updating: '.$groupCount.' group(s) - Using compression? '.($this->_compressedHeaders ? 'Yes' : 'No'), |
||
293 | __FUNCTION__, |
||
294 | 'header' |
||
295 | ); |
||
296 | |||
297 | // Loop through groups. |
||
298 | foreach ($groups as $group) { |
||
299 | $this->log( |
||
300 | 'Starting group '.$counter.' of '.$groupCount, |
||
301 | __FUNCTION__, |
||
302 | 'header' |
||
303 | ); |
||
304 | $this->updateGroup($group, $maxHeaders); |
||
305 | $counter++; |
||
306 | } |
||
307 | |||
308 | $this->log( |
||
309 | 'Updating completed in '.number_format(microtime(true) - $allTime, 2).' seconds.', |
||
310 | __FUNCTION__, |
||
311 | 'primary' |
||
312 | ); |
||
313 | } else { |
||
314 | $this->log( |
||
315 | 'No groups specified. Ensure groups are added to NNTmux\'s database for updating.', |
||
316 | __FUNCTION__, |
||
317 | 'warning' |
||
318 | ); |
||
319 | } |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * When the indexer is started, log the date/time. |
||
324 | */ |
||
325 | public function logIndexerStart(): void |
||
326 | { |
||
327 | Settings::query()->where('setting', '=', 'last_run_time')->update(['value' => Carbon::now()]); |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Download new headers for a single group. |
||
332 | * |
||
333 | * @param array $groupMySQL Array of MySQL results for a single group. |
||
334 | * @param int $maxHeaders (Optional) How many headers to download max. |
||
335 | * |
||
336 | * @return void |
||
337 | * @throws \Exception |
||
338 | */ |
||
339 | public function updateGroup($groupMySQL, $maxHeaders = 0): void |
||
340 | { |
||
341 | $startGroup = microtime(true); |
||
342 | |||
343 | $this->logIndexerStart(); |
||
344 | |||
345 | // Select the group on the NNTP server, gets the latest info on it. |
||
346 | $groupNNTP = $this->_nntp->selectGroup($groupMySQL['name']); |
||
347 | if ($this->_nntp->isError($groupNNTP)) { |
||
348 | $groupNNTP = $this->_nntp->dataError($this->_nntp, $groupMySQL['name']); |
||
349 | |||
350 | if (isset($groupNNTP['code']) && (int) $groupNNTP['code'] === 411) { |
||
351 | Group::disableIfNotExist($groupMySQL['id']); |
||
352 | } |
||
353 | if ($this->_nntp->isError($groupNNTP)) { |
||
354 | return; |
||
355 | } |
||
356 | } |
||
357 | |||
358 | if ($this->_echoCLI) { |
||
359 | ColorCLI::doEcho(ColorCLI::primary('Processing '.$groupMySQL['name']), true); |
||
360 | } |
||
361 | |||
362 | // Attempt to repair any missing parts before grabbing new ones. |
||
363 | if ((int) $groupMySQL['last_record'] !== 0) { |
||
364 | if ($this->_partRepair) { |
||
365 | if ($this->_echoCLI) { |
||
366 | ColorCLI::doEcho(ColorCLI::primary('Part repair enabled. Checking for missing parts.'), true); |
||
367 | } |
||
368 | $this->partRepair($groupMySQL); |
||
369 | |||
370 | $mgrPosters = $this->getMultiGroupPosters(); |
||
371 | if ($this->allAsMgr === true || ! empty($mgrPosters)) { |
||
372 | $tableNames = ProcessReleasesMultiGroup::tableNames(); |
||
373 | $this->partRepair($groupMySQL, $tableNames); |
||
374 | } |
||
375 | } elseif ($this->_echoCLI) { |
||
376 | ColorCLI::doEcho(ColorCLI::primary('Part repair disabled by user.'), true); |
||
377 | } |
||
378 | } |
||
379 | |||
380 | // Generate postdate for first record, for those that upgraded. |
||
381 | if ($groupMySQL['first_record_postdate'] === null && (int) $groupMySQL['first_record'] !== 0) { |
||
382 | $groupMySQL['first_record_postdate'] = $this->postdate($groupMySQL['first_record'], $groupNNTP); |
||
383 | Group::query()->where('id', $groupMySQL['id'])->update(['first_record_postdate' => Carbon::createFromTimestamp($groupMySQL['first_record_postdate'])]); |
||
384 | } |
||
385 | |||
386 | // Get first article we want aka the oldest. |
||
387 | if ((int) $groupMySQL['last_record'] === 0) { |
||
388 | if ($this->_newGroupScanByDays) { |
||
389 | // For new newsgroups - determine here how far we want to go back using date. |
||
390 | $first = $this->daytopost($this->_newGroupDaysToScan, $groupNNTP); |
||
391 | } elseif ($groupNNTP['first'] >= ($groupNNTP['last'] - ($this->_newGroupMessagesToScan + $this->messageBuffer))) { |
||
392 | // If what we want is lower than the groups first article, set the wanted first to the first. |
||
393 | $first = $groupNNTP['first']; |
||
394 | } else { |
||
395 | // Or else, use the newest article minus how much we should get for new groups. |
||
396 | $first = (string) ($groupNNTP['last'] - ($this->_newGroupMessagesToScan + $this->messageBuffer)); |
||
397 | } |
||
398 | |||
399 | // We will use this to subtract so we leave articles for the next time (in case the server doesn't have them yet) |
||
400 | $leaveOver = $this->messageBuffer; |
||
401 | |||
402 | // If this is not a new group, go from our newest to the servers newest. |
||
403 | } else { |
||
404 | // Set our oldest wanted to our newest local article. |
||
405 | $first = $groupMySQL['last_record']; |
||
406 | |||
407 | // This is how many articles we will grab. (the servers newest minus our newest). |
||
408 | $totalCount = (string) ($groupNNTP['last'] - $first); |
||
409 | |||
410 | // Check if the server has more articles than our loop limit x 2. |
||
411 | if ($totalCount > ($this->messageBuffer * 2)) { |
||
412 | // Get the remainder of $totalCount / $this->message buffer |
||
413 | $leaveOver = round($totalCount % $this->messageBuffer, 0, PHP_ROUND_HALF_DOWN) + $this->messageBuffer; |
||
414 | } else { |
||
415 | // Else get half of the available. |
||
416 | $leaveOver = round($totalCount / 2, 0, PHP_ROUND_HALF_DOWN); |
||
417 | } |
||
418 | } |
||
419 | |||
420 | // The last article we want, aka the newest. |
||
421 | $last = $groupLast = (string) ($groupNNTP['last'] - $leaveOver); |
||
422 | |||
423 | // If the newest we want is older than the oldest we want somehow.. set them equal. |
||
424 | if ($last < $first) { |
||
425 | $last = $groupLast = $first; |
||
426 | } |
||
427 | |||
428 | // This is how many articles we are going to get. |
||
429 | $total = (string) ($groupLast - $first); |
||
430 | // This is how many articles are available (without $leaveOver). |
||
431 | $realTotal = (string) ($groupNNTP['last'] - $first); |
||
432 | |||
433 | // Check if we should limit the amount of fetched new headers. |
||
434 | if ($maxHeaders > 0) { |
||
435 | if ($maxHeaders < ($groupLast - $first)) { |
||
436 | $groupLast = $last = (string) ($first + $maxHeaders); |
||
437 | } |
||
438 | $total = (string) ($groupLast - $first); |
||
439 | } |
||
440 | |||
441 | // If total is bigger than 0 it means we have new parts in the newsgroup. |
||
442 | if ($total > 0) { |
||
443 | if ($this->_echoCLI) { |
||
444 | ColorCLI::doEcho( |
||
445 | ColorCLI::primary( |
||
446 | ( |
||
447 | (int) $groupMySQL['last_record'] === 0 |
||
448 | ? 'New group '.$groupNNTP['group'].' starting with '. |
||
449 | ( |
||
450 | $this->_newGroupScanByDays |
||
451 | ? $this->_newGroupDaysToScan.' days' |
||
452 | : number_format($this->_newGroupMessagesToScan).' messages' |
||
453 | ).' worth.' |
||
454 | : 'Group '.$groupNNTP['group'].' has '.number_format($realTotal).' new articles.' |
||
455 | ). |
||
456 | ' Leaving '.number_format($leaveOver). |
||
457 | " for next pass.\nServer oldest: ".number_format($groupNNTP['first']). |
||
458 | ' Server newest: '.number_format($groupNNTP['last']). |
||
459 | ' Local newest: '.number_format($groupMySQL['last_record']) |
||
460 | ), |
||
461 | true |
||
462 | ); |
||
463 | } |
||
464 | |||
465 | $done = false; |
||
466 | // Get all the parts (in portions of $this->messageBuffer to not use too much memory). |
||
467 | while ($done === false) { |
||
468 | |||
469 | // Increment last until we reach $groupLast (group newest article). |
||
470 | if ($total > $this->messageBuffer) { |
||
471 | if ((string) ($first + $this->messageBuffer) > $groupLast) { |
||
472 | $last = $groupLast; |
||
473 | } else { |
||
474 | $last = (string) ($first + $this->messageBuffer); |
||
475 | } |
||
476 | } |
||
477 | // Increment first so we don't get an article we already had. |
||
478 | $first++; |
||
479 | |||
480 | if ($this->_echoCLI) { |
||
481 | ColorCLI::doEcho( |
||
482 | ColorCLI::header( |
||
483 | PHP_EOL.'Getting '.number_format($last - $first + 1).' articles ('.number_format($first). |
||
484 | ' to '.number_format($last).') from '.$groupMySQL['name'].' - ('. |
||
485 | number_format($groupLast - $last).' articles in queue).' |
||
486 | ) |
||
487 | ); |
||
488 | } |
||
489 | |||
490 | // Get article headers from newsgroup. |
||
491 | $scanSummary = $this->scan($groupMySQL, $first, $last); |
||
492 | |||
493 | // Check if we fetched headers. |
||
494 | if (! empty($scanSummary)) { |
||
495 | |||
496 | // If new group, update first record & postdate |
||
497 | if ($groupMySQL['first_record_postdate'] === null && (int) $groupMySQL['first_record'] === 0) { |
||
498 | $groupMySQL['first_record'] = $scanSummary['firstArticleNumber']; |
||
499 | |||
500 | if (isset($scanSummary['firstArticleDate'])) { |
||
501 | $groupMySQL['first_record_postdate'] = strtotime($scanSummary['firstArticleDate']); |
||
502 | } else { |
||
503 | $groupMySQL['first_record_postdate'] = $this->postdate($groupMySQL['first_record'], $groupNNTP); |
||
504 | } |
||
505 | |||
506 | Group::query() |
||
507 | ->where('id', $groupMySQL['id']) |
||
508 | ->update( |
||
509 | [ |
||
510 | 'first_record' => $scanSummary['firstArticleNumber'], |
||
511 | 'first_record_postdate' => Carbon::createFromTimestamp( |
||
512 | $groupMySQL['first_record_postdate'] |
||
513 | ), |
||
514 | ] |
||
515 | ); |
||
516 | } |
||
517 | |||
518 | $scanSummary['lastArticleDate'] = (isset($scanSummary['lastArticleDate']) ? strtotime($scanSummary['lastArticleDate']) : false); |
||
519 | if (! is_numeric($scanSummary['lastArticleDate'])) { |
||
520 | $scanSummary['lastArticleDate'] = $this->postdate($scanSummary['lastArticleNumber'], $groupNNTP); |
||
521 | } |
||
522 | |||
523 | Group::query() |
||
524 | ->where('id', $groupMySQL['id']) |
||
525 | ->update( |
||
526 | [ |
||
527 | 'last_record' => $scanSummary['lastArticleNumber'], |
||
528 | 'last_record_postdate' => Carbon::createFromTimestamp($scanSummary['lastArticleDate']), |
||
529 | 'last_updated' => Carbon::now(), |
||
530 | ] |
||
531 | ); |
||
532 | } else { |
||
533 | // If we didn't fetch headers, update the record still. |
||
534 | Group::query() |
||
535 | ->where('id', $groupMySQL['id']) |
||
536 | ->update( |
||
537 | [ |
||
538 | 'last_record' => $last, |
||
539 | 'last_updated' => Carbon::now(), |
||
540 | ] |
||
541 | ); |
||
542 | } |
||
543 | |||
544 | if ((int) $last === (int) $groupLast) { |
||
545 | $done = true; |
||
546 | } else { |
||
547 | $first = $last; |
||
548 | } |
||
549 | } |
||
550 | |||
551 | if ($this->_echoCLI) { |
||
552 | ColorCLI::doEcho( |
||
553 | ColorCLI::primary( |
||
554 | PHP_EOL.'Group '.$groupMySQL['name'].' processed in '. |
||
555 | number_format(microtime(true) - $startGroup, 2).' seconds.' |
||
556 | ), |
||
557 | true |
||
558 | ); |
||
559 | } |
||
560 | } elseif ($this->_echoCLI) { |
||
561 | ColorCLI::doEcho( |
||
562 | ColorCLI::primary( |
||
563 | 'No new articles for '.$groupMySQL['name'].' (first '.number_format($first). |
||
564 | ', last '.number_format($last).', grouplast '.number_format($groupMySQL['last_record']). |
||
565 | ', total '.number_format($total).")\n".'Server oldest: '.number_format($groupNNTP['first']). |
||
566 | ' Server newest: '.number_format($groupNNTP['last']).' Local newest: '.number_format($groupMySQL['last_record']) |
||
567 | ), |
||
568 | true |
||
569 | ); |
||
570 | } |
||
571 | } |
||
572 | |||
573 | /** |
||
574 | * Loop over range of wanted headers, insert headers into DB. |
||
575 | * |
||
576 | * @param array $groupMySQL The group info from mysql. |
||
577 | * @param int $first The oldest wanted header. |
||
578 | * @param int $last The newest wanted header. |
||
579 | * @param string $type Is this partrepair or update or backfill? |
||
580 | * @param null|array $missingParts If we are running in partrepair, the list of missing article numbers. |
||
581 | * |
||
582 | * @return array Empty on failure. |
||
583 | * @throws \Exception |
||
584 | */ |
||
585 | public function scan($groupMySQL, $first, $last, $type = 'update', $missingParts = null): array |
||
586 | { |
||
587 | // Start time of scan method and of fetching headers. |
||
588 | $this->startLoop = microtime(true); |
||
589 | $this->groupMySQL = $groupMySQL; |
||
590 | $this->last = $last; |
||
591 | $this->first = $first; |
||
592 | |||
593 | $this->notYEnc = $this->headersBlackListed = 0; |
||
594 | |||
595 | // Check if MySQL tables exist, create if they do not, get their names at the same time. |
||
596 | $this->tableNames = (new Group())->getCBPTableNames($this->groupMySQL['id']); |
||
597 | |||
598 | $mgrPosters = $this->getMultiGroupPosters(); |
||
599 | |||
600 | if ($this->allAsMgr === true || ! empty($mgrPosters)) { |
||
601 | $mgrActive = true; |
||
602 | $mgrPosters = ! empty($mgrPosters) ? array_flip(array_column($mgrPosters, 'poster')) : ''; |
||
603 | } else { |
||
604 | $mgrActive = false; |
||
605 | } |
||
606 | |||
607 | $returnArray = $stdHeaders = $mgrHeaders = []; |
||
608 | |||
609 | $partRepair = ($type === 'partrepair'); |
||
610 | $this->addToPartRepair = ($type === 'update' && $this->_partRepair); |
||
611 | |||
612 | // Download the headers. |
||
613 | if ($partRepair === true) { |
||
614 | // This is slower but possibly is better with missing headers. |
||
615 | $headers = $this->_nntp->getOverview($this->first.'-'.$this->last, true, false); |
||
616 | } else { |
||
617 | $headers = $this->_nntp->getXOVER($this->first.'-'.$this->last); |
||
618 | } |
||
619 | |||
620 | // If there was an error, try to reconnect. |
||
621 | if ($this->_nntp->isError($headers)) { |
||
622 | |||
623 | // Increment if part repair and return false. |
||
624 | if ($partRepair === true) { |
||
625 | $this->_pdo->queryExec( |
||
626 | sprintf( |
||
627 | 'UPDATE %s SET attempts = attempts + 1 WHERE groups_id = %d AND numberid %s', |
||
628 | $this->tableNames['prname'], |
||
629 | $this->groupMySQL['id'], |
||
630 | ((int) $this->first === (int) $this->last ? '= '.$this->first : 'IN ('.implode(',', range($this->first, $this->last)).')') |
||
631 | ) |
||
632 | ); |
||
633 | |||
634 | return $returnArray; |
||
635 | } |
||
636 | |||
637 | // This is usually a compression error, so try disabling compression. |
||
638 | $this->_nntp->doQuit(); |
||
639 | if ($this->_nntp->doConnect(false) !== true) { |
||
640 | return $returnArray; |
||
641 | } |
||
642 | |||
643 | // Re-select group, download headers again without compression and re-enable compression. |
||
644 | $this->_nntp->selectGroup($this->groupMySQL['name']); |
||
645 | $headers = $this->_nntp->getXOVER($this->first.'-'.$this->last); |
||
646 | $this->_nntp->enableCompression(); |
||
647 | |||
648 | // Check if the non-compression headers have an error. |
||
649 | if ($this->_nntp->isError($headers)) { |
||
650 | $message = ((int) $headers->code === 0 ? 'Unknown error' : $headers->message); |
||
651 | $this->log( |
||
652 | "Code {$headers->code}: $message\nSkipping group: {$this->groupMySQL['name']}", |
||
653 | __FUNCTION__, |
||
654 | 'error' |
||
655 | ); |
||
656 | |||
657 | return $returnArray; |
||
658 | } |
||
659 | } |
||
660 | |||
661 | // Start of processing headers. |
||
662 | $this->startCleaning = microtime(true); |
||
663 | |||
664 | // End of the getting data from usenet. |
||
665 | $this->timeHeaders = number_format($this->startCleaning - $this->startLoop, 2); |
||
666 | |||
667 | // Check if we got headers. |
||
668 | $msgCount = \count($headers); |
||
669 | |||
670 | if ($msgCount < 1) { |
||
671 | return $returnArray; |
||
672 | } |
||
673 | |||
674 | $this->getHighLowArticleInfo($returnArray, $headers, $msgCount); |
||
675 | |||
676 | $headersRepaired = $rangeNotReceived = $this->headersReceived = $this->headersNotInserted = []; |
||
677 | |||
678 | foreach ($headers as $header) { |
||
679 | |||
680 | // Check if we got the article or not. |
||
681 | if (isset($header['Number'])) { |
||
682 | $this->headersReceived[] = $header['Number']; |
||
683 | } else { |
||
684 | if ($this->addToPartRepair) { |
||
685 | $rangeNotReceived[] = $header['Number']; |
||
686 | } |
||
687 | continue; |
||
688 | } |
||
689 | |||
690 | // If set we are running in partRepair mode. |
||
691 | if ($partRepair === true && $missingParts !== null) { |
||
692 | if (! \in_array($header['Number'], $missingParts, false)) { |
||
693 | // If article isn't one that is missing skip it. |
||
694 | continue; |
||
695 | } |
||
696 | // We got the part this time. Remove article from part repair. |
||
697 | $headersRepaired[] = $header['Number']; |
||
698 | } |
||
699 | |||
700 | /* |
||
701 | * Find part / total parts. Ignore if no part count found. |
||
702 | * |
||
703 | * \s* Trims the leading space. |
||
704 | * (?!"Usenet Index Post) ignores these types of articles, they are useless. |
||
705 | * (.+) Fetches the subject. |
||
706 | * \s+ Trims trailing space after the subject. |
||
707 | * \((\d+)\/(\d+)\) Gets the part count. |
||
708 | * No ending ($) as there are cases of subjects with extra data after the part count. |
||
709 | */ |
||
710 | if (preg_match('/^\s*(?!"Usenet Index Post)(.+)\s+\((\d+)\/(\d+)\)/', $header['Subject'], $header['matches'])) { |
||
711 | // Add yEnc to subjects that do not have them, but have the part number at the end of the header. |
||
712 | if (stripos($header['Subject'], 'yEnc') === false) { |
||
713 | $header['matches'][1] .= ' yEnc'; |
||
714 | } |
||
715 | } else { |
||
716 | if ($this->_showDroppedYEncParts === true && strpos($header['Subject'], '"Usenet Index Post') !== 0) { |
||
717 | file_put_contents( |
||
718 | NN_LOGS.'not_yenc'.$this->groupMySQL['name'].'.dropped.log', |
||
719 | $header['Subject'].PHP_EOL, |
||
720 | FILE_APPEND |
||
721 | ); |
||
722 | } |
||
723 | $this->notYEnc++; |
||
724 | continue; |
||
725 | } |
||
726 | |||
727 | // Filter subject based on black/white list. |
||
728 | if ($this->isBlackListed($header, $this->groupMySQL['name'])) { |
||
729 | $this->headersBlackListed++; |
||
730 | continue; |
||
731 | } |
||
732 | |||
733 | if (! isset($header['Bytes'])) { |
||
734 | $header['Bytes'] = (isset($this->header[':bytes']) ? $header[':bytes'] : 0); |
||
735 | } |
||
736 | $header['Bytes'] = (int) $header['Bytes']; |
||
737 | |||
738 | if ($this->allAsMgr === true || ($mgrActive === true && array_key_exists($header['From'], $mgrPosters))) { |
||
739 | $mgrHeaders[] = $header; |
||
740 | } else { |
||
741 | $stdHeaders[] = $header; |
||
742 | } |
||
743 | } |
||
744 | |||
745 | unset($headers); // Reclaim memory now that headers are split. |
||
746 | |||
747 | if (! empty($this->_binaryBlacklistIdsToUpdate)) { |
||
748 | $this->updateBlacklistUsage(); |
||
749 | } |
||
750 | |||
751 | if ($this->_echoCLI && $partRepair === false) { |
||
752 | $this->outputHeaderInitial(); |
||
753 | } |
||
754 | |||
755 | // MGR headers goes first |
||
756 | if (! empty($mgrHeaders)) { |
||
757 | $this->tableNames = ProcessReleasesMultiGroup::tableNames(); |
||
758 | $this->storeHeaders($mgrHeaders, true); |
||
759 | } |
||
760 | unset($mgrHeaders); |
||
761 | |||
762 | // Standard headers go second so we can switch tableNames back and do part repair to standard group tables |
||
763 | if (! empty($stdHeaders)) { |
||
764 | $this->tableNames = (new Group())->getCBPTableNames($this->groupMySQL['id']); |
||
765 | $this->storeHeaders($stdHeaders, false); |
||
766 | } |
||
767 | unset($stdHeaders); |
||
768 | |||
769 | // Start of part repair. |
||
770 | $this->startPR = microtime(true); |
||
771 | |||
772 | // End of inserting. |
||
773 | $this->timeInsert = number_format($this->startPR - $this->startUpdate, 2); |
||
774 | |||
775 | if ($partRepair && \count($headersRepaired) > 0) { |
||
776 | $this->removeRepairedParts($headersRepaired, $this->tableNames['prname'], $this->groupMySQL['id']); |
||
777 | } |
||
778 | unset($headersRepaired); |
||
779 | |||
780 | if ($this->addToPartRepair) { |
||
781 | $notInsertedCount = \count($this->headersNotInserted); |
||
782 | if ($notInsertedCount > 0) { |
||
783 | $this->addMissingParts($this->headersNotInserted, $this->tableNames['prname'], $this->groupMySQL['id']); |
||
784 | |||
785 | $this->log( |
||
786 | $notInsertedCount.' articles failed to insert!', |
||
787 | __FUNCTION__, |
||
788 | 'warning' |
||
789 | ); |
||
790 | } |
||
791 | unset($this->headersNotInserted); |
||
792 | |||
793 | // Check if we have any missing headers. |
||
794 | if (($this->last - $this->first - $this->notYEnc - $this->headersBlackListed + 1) > \count($this->headersReceived)) { |
||
795 | $rangeNotReceived = array_merge($rangeNotReceived, array_diff(range($this->first, $this->last), $this->headersReceived)); |
||
796 | } |
||
797 | $notReceivedCount = \count($rangeNotReceived); |
||
798 | if ($notReceivedCount > 0) { |
||
799 | $this->addMissingParts($rangeNotReceived, $this->tableNames['prname'], $this->groupMySQL['id']); |
||
800 | |||
801 | if ($this->_echoCLI) { |
||
802 | ColorCLI::doEcho( |
||
803 | ColorCLI::alternate( |
||
804 | 'Server did not return '.$notReceivedCount. |
||
805 | ' articles from '.$this->groupMySQL['name'].'.' |
||
806 | ), |
||
807 | true |
||
808 | ); |
||
809 | } |
||
810 | } |
||
811 | unset($rangeNotReceived); |
||
812 | } |
||
813 | |||
814 | $this->outputHeaderDuration(); |
||
815 | |||
816 | return $returnArray; |
||
817 | } |
||
818 | |||
819 | /** |
||
820 | * Parse headers into collections/binaries and store header data as parts. |
||
821 | * |
||
822 | * @param array $headers The retrieved headers |
||
823 | * @param bool $multiGroup Is this task being run in MGR mode? |
||
824 | * |
||
825 | * @throws \Exception |
||
826 | */ |
||
827 | protected function storeHeaders(array $headers, $multiGroup): void |
||
828 | { |
||
829 | $this->multiGroup = $multiGroup; |
||
830 | $binariesUpdate = $collectionIDs = $articles = []; |
||
831 | |||
832 | $this->_pdo->beginTransaction(); |
||
833 | |||
834 | $partsQuery = $partsCheck = |
||
835 | "INSERT IGNORE INTO {$this->tableNames['pname']} (binaries_id, number, messageid, partnumber, size) VALUES "; |
||
836 | |||
837 | // Loop articles, figure out files/parts. |
||
838 | foreach ($headers as $this->header) { |
||
839 | // Set up the info for inserting into parts/binaries/collections tables. |
||
840 | if (! isset($articles[$this->header['matches'][1]])) { |
||
841 | |||
842 | // check whether file count should be ignored (XXX packs for now only). |
||
843 | $whitelistMatch = false; |
||
844 | if ($this->_ignoreFileCount($this->groupMySQL['name'], $this->header['matches'][1])) { |
||
845 | $whitelistMatch = true; |
||
846 | $fileCount[1] = $fileCount[3] = 0; |
||
847 | } |
||
848 | |||
849 | // Attempt to find the file count. If it is not found, set it to 0. |
||
850 | if (! $whitelistMatch && ! preg_match('/[[(\s](\d{1,5})(\/|[\s_]of[\s_]|-)(\d{1,5})[])\s$:]/i', $this->header['matches'][1], $fileCount)) { |
||
851 | $fileCount[1] = $fileCount[3] = 0; |
||
852 | if ($this->_showDroppedYEncParts === true) { |
||
853 | file_put_contents( |
||
854 | NN_LOGS.'no_files'.$this->groupMySQL['name'].'.log', |
||
855 | $this->header['Subject'].PHP_EOL, |
||
856 | FILE_APPEND |
||
857 | ); |
||
858 | } |
||
859 | } |
||
860 | |||
861 | if ($this->multiGroup) { |
||
862 | $ckName = ''; |
||
863 | $ckId = ''; |
||
864 | } else { |
||
865 | $ckName = $this->groupMySQL['name']; |
||
866 | $ckId = $this->groupMySQL['id']; |
||
867 | } |
||
868 | |||
869 | $collMatch = $this->_collectionsCleaning->collectionsCleaner( |
||
870 | $this->header['matches'][1], |
||
871 | $ckName |
||
872 | ); |
||
873 | |||
874 | // Used to group articles together when forming the release. MGR requires this to be group irrespective |
||
875 | $this->header['CollectionKey'] = $collMatch['name'].$ckId.$fileCount[3]; |
||
876 | |||
877 | // If this header's collection key isn't in memory, attempt to insert the collection |
||
878 | if (! isset($collectionIDs[$this->header['CollectionKey']])) { |
||
879 | |||
880 | /* Date from header should be a string this format: |
||
881 | * 31 Mar 2014 15:36:04 GMT or 6 Oct 1998 04:38:40 -0500 |
||
882 | * Still make sure it's not unix time, convert it to unix time if it is. |
||
883 | */ |
||
884 | $this->header['Date'] = (is_numeric($this->header['Date']) ? $this->header['Date'] : strtotime($this->header['Date'])); |
||
885 | |||
886 | // Get the current unixtime from PHP. |
||
887 | $now = Carbon::now()->timestamp; |
||
888 | |||
889 | $xref = ($this->multiGroup === true ? sprintf('xref = CONCAT(xref, "\\n"%s ),', $this->_pdo->escapeString(substr($this->header['Xref'], 2, 255))) : ''); |
||
890 | $date = $this->header['Date'] > $now ? $now : $this->header['Date']; |
||
891 | $unixtime = is_numeric($this->header['Date']) ? $date : $now; |
||
892 | |||
893 | $random = random_bytes(16); |
||
894 | |||
895 | $collectionID = $this->_pdo->queryInsert( |
||
896 | sprintf( |
||
897 | " |
||
898 | INSERT INTO %s (subject, fromname, date, xref, groups_id, |
||
899 | totalfiles, collectionhash, collection_regexes_id, dateadded) |
||
900 | VALUES (%s, %s, FROM_UNIXTIME(%s), %s, %d, %d, '%s', %d, NOW()) |
||
901 | ON DUPLICATE KEY UPDATE %s dateadded = NOW(), noise = '%s'", |
||
902 | $this->tableNames['cname'], |
||
903 | $this->_pdo->escapeString(substr(utf8_encode($this->header['matches'][1]), 0, 255)), |
||
904 | $this->_pdo->escapeString(utf8_encode($this->header['From'])), |
||
905 | $unixtime, |
||
906 | $this->_pdo->escapeString(substr($this->header['Xref'], 0, 255)), |
||
907 | $this->groupMySQL['id'], |
||
908 | $fileCount[3], |
||
909 | sha1($this->header['CollectionKey']), |
||
910 | $collMatch['id'], |
||
911 | $xref, |
||
912 | sodium_bin2hex($random) |
||
913 | ) |
||
914 | ); |
||
915 | |||
916 | if ($collectionID === false) { |
||
917 | if ($this->addToPartRepair) { |
||
918 | $this->headersNotInserted[] = $this->header['Number']; |
||
919 | } |
||
920 | $this->_pdo->Rollback(); |
||
921 | $this->_pdo->beginTransaction(); |
||
922 | continue; |
||
923 | } |
||
924 | $collectionIDs[$this->header['CollectionKey']] = $collectionID; |
||
925 | } else { |
||
926 | $collectionID = $collectionIDs[$this->header['CollectionKey']]; |
||
927 | } |
||
928 | |||
929 | // MGR or Standard, Binary Hash should be unique to the group |
||
930 | $hash = md5($this->header['matches'][1].$this->header['From'].$this->groupMySQL['id']); |
||
931 | |||
932 | $binaryID = $this->_pdo->queryInsert( |
||
933 | sprintf( |
||
934 | " |
||
935 | INSERT INTO %s (binaryhash, name, collections_id, totalparts, currentparts, filenumber, partsize) |
||
936 | VALUES (UNHEX('%s'), %s, %d, %d, 1, %d, %d) |
||
937 | ON DUPLICATE KEY UPDATE currentparts = currentparts + 1, partsize = partsize + %d", |
||
938 | $this->tableNames['bname'], |
||
939 | $hash, |
||
940 | $this->_pdo->escapeString(utf8_encode($this->header['matches'][1])), |
||
941 | $collectionID, |
||
942 | $this->header['matches'][3], |
||
943 | $fileCount[1], |
||
944 | $this->header['Bytes'], |
||
945 | $this->header['Bytes'] |
||
946 | ) |
||
947 | ); |
||
948 | |||
949 | if ($binaryID === false) { |
||
950 | if ($this->addToPartRepair) { |
||
951 | $this->headersNotInserted[] = $this->header['Number']; |
||
952 | } |
||
953 | $this->_pdo->Rollback(); |
||
954 | $this->_pdo->beginTransaction(); |
||
955 | continue; |
||
956 | } |
||
957 | |||
958 | $binariesUpdate[$binaryID]['Size'] = 0; |
||
959 | $binariesUpdate[$binaryID]['Parts'] = 0; |
||
960 | |||
961 | $articles[$this->header['matches'][1]]['CollectionID'] = $collectionID; |
||
962 | $articles[$this->header['matches'][1]]['BinaryID'] = $binaryID; |
||
963 | } else { |
||
964 | $binaryID = $articles[$this->header['matches'][1]]['BinaryID']; |
||
965 | $binariesUpdate[$binaryID]['Size'] += $this->header['Bytes']; |
||
966 | $binariesUpdate[$binaryID]['Parts']++; |
||
967 | } |
||
968 | |||
969 | // Strip the < and >, saves space in DB. |
||
970 | $this->header['Message-ID'][0] = "'"; |
||
971 | |||
972 | $partsQuery .= |
||
973 | '('.$binaryID.','.$this->header['Number'].','.rtrim($this->header['Message-ID'], '>')."',". |
||
974 | $this->header['matches'][2].','.$this->header['Bytes'].'),'; |
||
975 | } |
||
976 | |||
977 | unset($headers); // Reclaim memory. |
||
978 | |||
979 | // Start of inserting into SQL. |
||
980 | $this->startUpdate = microtime(true); |
||
981 | |||
982 | // End of processing headers. |
||
983 | $this->timeCleaning = number_format($this->startUpdate - $this->startCleaning, 2); |
||
984 | $binariesQuery = $binariesCheck = sprintf('INSERT INTO %s (id, partsize, currentparts) VALUES ', $this->tableNames['bname']); |
||
985 | foreach ($binariesUpdate as $binaryID => $binary) { |
||
986 | $binariesQuery .= '('.$binaryID.','.$binary['Size'].','.$binary['Parts'].'),'; |
||
987 | } |
||
988 | $binariesEnd = ' ON DUPLICATE KEY UPDATE partsize = VALUES(partsize) + partsize, currentparts = VALUES(currentparts) + currentparts'; |
||
989 | $binariesQuery = rtrim($binariesQuery, ',').$binariesEnd; |
||
990 | |||
991 | // Check if we got any binaries. If we did, try to insert them. |
||
992 | if (\strlen($binariesCheck.$binariesEnd) === \strlen($binariesQuery) ? true : $this->_pdo->queryExec($binariesQuery)) { |
||
993 | if ($this->_debug) { |
||
994 | ColorCLI::doEcho( |
||
995 | ColorCLI::debug( |
||
996 | 'Sending '.round(\strlen($partsQuery) / 1024, 2). |
||
997 | ' KB of'.($this->multiGroup ? ' MGR' : '').' parts to MySQL' |
||
998 | ), true |
||
999 | ); |
||
1000 | } |
||
1001 | if (\strlen($partsQuery) === \strlen($partsCheck) ? true : $this->_pdo->queryExec(rtrim($partsQuery, ','))) { |
||
1002 | $this->_pdo->Commit(); |
||
1003 | } else { |
||
1004 | if ($this->addToPartRepair) { |
||
1005 | $this->headersNotInserted += $this->headersReceived; |
||
1006 | } |
||
1007 | $this->_pdo->Rollback(); |
||
1008 | } |
||
1009 | } else { |
||
1010 | if ($this->addToPartRepair) { |
||
1011 | $this->headersNotInserted += $this->headersReceived; |
||
1012 | } |
||
1013 | $this->_pdo->Rollback(); |
||
1014 | } |
||
1015 | } |
||
1016 | |||
1017 | /** |
||
1018 | * Gets the First and Last Article Number and Date for the received headers. |
||
1019 | * |
||
1020 | * @param array $returnArray |
||
1021 | * @param array $headers |
||
1022 | * @param int $msgCount |
||
1023 | */ |
||
1024 | protected function getHighLowArticleInfo(array &$returnArray, array $headers, int $msgCount): void |
||
1025 | { |
||
1026 | // Get highest and lowest article numbers/dates. |
||
1027 | $iterator1 = 0; |
||
1028 | $iterator2 = $msgCount - 1; |
||
1029 | while (true) { |
||
1030 | if (! isset($returnArray['firstArticleNumber']) && isset($headers[$iterator1]['Number'])) { |
||
1031 | $returnArray['firstArticleNumber'] = $headers[$iterator1]['Number']; |
||
1032 | $returnArray['firstArticleDate'] = $headers[$iterator1]['Date']; |
||
1033 | } |
||
1034 | |||
1035 | if (! isset($returnArray['lastArticleNumber']) && isset($headers[$iterator2]['Number'])) { |
||
1036 | $returnArray['lastArticleNumber'] = $headers[$iterator2]['Number']; |
||
1037 | $returnArray['lastArticleDate'] = $headers[$iterator2]['Date']; |
||
1038 | } |
||
1039 | |||
1040 | // Break if we found non empty articles. |
||
1041 | if (isset($returnArray['firstArticleNumber, lastArticleNumber'])) { |
||
1042 | break; |
||
1043 | } |
||
1044 | |||
1045 | // Break out if we couldn't find anything. |
||
1046 | if ($iterator1++ >= $msgCount - 1 || $iterator2-- <= 0) { |
||
1047 | break; |
||
1048 | } |
||
1049 | } |
||
1050 | } |
||
1051 | |||
1052 | /** |
||
1053 | * Updates Blacklist Regex Timers in DB to reflect last usage. |
||
1054 | */ |
||
1055 | protected function updateBlacklistUsage(): void |
||
1056 | { |
||
1057 | BinaryBlacklist::query()->whereIn('id', $this->_binaryBlacklistIdsToUpdate)->update(['last_activity' => Carbon::now()]); |
||
1058 | $this->_binaryBlacklistIdsToUpdate = []; |
||
1059 | } |
||
1060 | |||
1061 | /** |
||
1062 | * Outputs the initial header scan results after yEnc check and blacklist routines. |
||
1063 | */ |
||
1064 | protected function outputHeaderInitial(): void |
||
1065 | { |
||
1066 | ColorCLI::doEcho( |
||
1067 | ColorCLI::primary( |
||
1068 | 'Received '.\count($this->headersReceived). |
||
1069 | ' articles of '.number_format($this->last - $this->first + 1).' requested, '. |
||
1070 | $this->headersBlackListed.' blacklisted, '.$this->notYEnc.' not yEnc.' |
||
1071 | ), true |
||
1072 | ); |
||
1073 | } |
||
1074 | |||
1075 | /** |
||
1076 | * Outputs speed metrics of the scan function to CLI. |
||
1077 | */ |
||
1078 | protected function outputHeaderDuration(): void |
||
1079 | { |
||
1080 | $currentMicroTime = microtime(true); |
||
1081 | if ($this->_echoCLI) { |
||
1082 | ColorCLI::doEcho( |
||
1083 | ColorCLI::alternateOver($this->timeHeaders.'s'). |
||
1084 | ColorCLI::primaryOver(' to download articles, '). |
||
1085 | ColorCLI::alternateOver($this->timeCleaning.'s'). |
||
1086 | ColorCLI::primaryOver(' to process collections, '). |
||
1087 | ColorCLI::alternateOver($this->timeInsert.'s'). |
||
1088 | ColorCLI::primaryOver(' to insert binaries/parts, '). |
||
1089 | ColorCLI::alternateOver(number_format($currentMicroTime - $this->startPR, 2).'s'). |
||
1090 | ColorCLI::primaryOver(' for part repair, '). |
||
1091 | ColorCLI::alternateOver(number_format($currentMicroTime - $this->startLoop, 2).'s'). |
||
1092 | ColorCLI::primary(' total.'), true); |
||
1093 | } |
||
1094 | } |
||
1095 | |||
1096 | /** |
||
1097 | * If we failed to insert Collections/Binaries/Parts, rollback the transaction and add the parts to part repair. |
||
1098 | * |
||
1099 | * @param array $headers Array of headers containing sub-arrays with parts. |
||
1100 | * |
||
1101 | * @return array Array of article numbers to add to part repair. |
||
1102 | */ |
||
1103 | protected function _rollbackAddToPartRepair(array $headers): array |
||
1104 | { |
||
1105 | $headersNotInserted = []; |
||
1106 | foreach ($headers as $header) { |
||
1107 | foreach ($header as $file) { |
||
1108 | $headersNotInserted[] = $file['Parts']['number']; |
||
1109 | } |
||
1110 | } |
||
1111 | $this->_pdo->Rollback(); |
||
1112 | |||
1113 | return $headersNotInserted; |
||
1114 | } |
||
1115 | |||
1116 | /** |
||
1117 | * Attempt to get missing article headers. |
||
1118 | * |
||
1119 | * @param array|string $tables |
||
1120 | * @param array $groupArr The info for this group from mysql. |
||
1121 | * |
||
1122 | * @return void |
||
1123 | * @throws \Exception |
||
1124 | */ |
||
1125 | public function partRepair($groupArr, $tables = ''): void |
||
1126 | { |
||
1127 | $tableNames = $tables; |
||
1128 | |||
1129 | if ($tableNames === '') { |
||
1130 | $tableNames = (new Group())->getCBPTableNames($groupArr['id']); |
||
1131 | } |
||
1132 | // Get all parts in partrepair table. |
||
1133 | $missingParts = $this->_pdo->query( |
||
1134 | sprintf( |
||
1135 | ' |
||
1136 | SELECT * FROM %s |
||
1137 | WHERE groups_id = %d AND attempts < %d |
||
1138 | ORDER BY numberid ASC LIMIT %d', |
||
1139 | $tableNames['prname'], |
||
1140 | $groupArr['id'], |
||
1141 | $this->_partRepairMaxTries, |
||
1142 | $this->_partRepairLimit |
||
1143 | ) |
||
1144 | ); |
||
1145 | |||
1146 | $missingCount = \count($missingParts); |
||
1147 | if ($missingCount > 0) { |
||
1148 | if ($this->_echoCLI) { |
||
1149 | ColorCLI::doEcho( |
||
1150 | ColorCLI::primary( |
||
1151 | 'Attempting to repair '. |
||
1152 | number_format($missingCount). |
||
1153 | ' parts.' |
||
1154 | ), |
||
1155 | true |
||
1156 | ); |
||
1157 | } |
||
1158 | |||
1159 | // Loop through each part to group into continuous ranges with a maximum range of messagebuffer/4. |
||
1160 | $ranges = $partList = []; |
||
1161 | $firstPart = $lastNum = $missingParts[0]['numberid']; |
||
1162 | |||
1163 | foreach ($missingParts as $part) { |
||
1164 | if (($part['numberid'] - $firstPart) > ($this->messageBuffer / 4)) { |
||
1165 | $ranges[] = [ |
||
1166 | 'partfrom' => $firstPart, |
||
1167 | 'partto' => $lastNum, |
||
1168 | 'partlist' => $partList, |
||
1169 | ]; |
||
1170 | |||
1171 | $firstPart = $part['numberid']; |
||
1172 | $partList = []; |
||
1173 | } |
||
1174 | $partList[] = $part['numberid']; |
||
1175 | $lastNum = $part['numberid']; |
||
1176 | } |
||
1177 | |||
1178 | $ranges[] = [ |
||
1179 | 'partfrom' => $firstPart, |
||
1180 | 'partto' => $lastNum, |
||
1181 | 'partlist' => $partList, |
||
1182 | ]; |
||
1183 | |||
1184 | // Download missing parts in ranges. |
||
1185 | foreach ($ranges as $range) { |
||
1186 | $partFrom = $range['partfrom']; |
||
1187 | $partTo = $range['partto']; |
||
1188 | $partList = $range['partlist']; |
||
1189 | |||
1190 | if ($this->_echoCLI) { |
||
1191 | echo \chr(random_int(45, 46)).PHP_EOL; |
||
1192 | } |
||
1193 | |||
1194 | // Get article headers from newsgroup. |
||
1195 | $this->scan($groupArr, $partFrom, $partTo, 'missed_parts', $partList); |
||
1196 | } |
||
1197 | |||
1198 | // Calculate parts repaired |
||
1199 | $result = $this->_pdo->queryOneRow( |
||
1200 | sprintf( |
||
1201 | ' |
||
1202 | SELECT COUNT(id) AS num |
||
1203 | FROM %s |
||
1204 | WHERE groups_id = %d |
||
1205 | AND numberid <= %d', |
||
1206 | $tableNames['prname'], |
||
1207 | $groupArr['id'], |
||
1208 | $missingParts[$missingCount - 1]['numberid'] |
||
1209 | ) |
||
1210 | ); |
||
1211 | |||
1212 | $partsRepaired = 0; |
||
1213 | if ($result !== false) { |
||
1214 | $partsRepaired = ($missingCount - $result['num']); |
||
1215 | } |
||
1216 | |||
1217 | // Update attempts on remaining parts for active group |
||
1218 | if (isset($missingParts[$missingCount - 1]['id'])) { |
||
1219 | $this->_pdo->queryExec( |
||
1220 | sprintf( |
||
1221 | ' |
||
1222 | UPDATE %s |
||
1223 | SET attempts = attempts + 1 |
||
1224 | WHERE groups_id = %d |
||
1225 | AND numberid <= %d', |
||
1226 | $tableNames['prname'], |
||
1227 | $groupArr['id'], |
||
1228 | $missingParts[$missingCount - 1]['numberid'] |
||
1229 | ) |
||
1230 | ); |
||
1231 | } |
||
1232 | |||
1233 | if ($this->_echoCLI) { |
||
1234 | ColorCLI::doEcho( |
||
1235 | ColorCLI::primary( |
||
1236 | PHP_EOL. |
||
1237 | number_format($partsRepaired). |
||
1238 | ' parts repaired.' |
||
1239 | ), |
||
1240 | true |
||
1241 | ); |
||
1242 | } |
||
1243 | } |
||
1244 | |||
1245 | // Remove articles that we cant fetch after x attempts. |
||
1246 | $this->_pdo->queryExec( |
||
1247 | sprintf( |
||
1248 | 'DELETE FROM %s WHERE attempts >= %d AND groups_id = %d', |
||
1249 | $tableNames['prname'], |
||
1250 | $this->_partRepairMaxTries, |
||
1251 | $groupArr['id'] |
||
1252 | ) |
||
1253 | ); |
||
1254 | } |
||
1255 | |||
1256 | /** |
||
1257 | * Returns unix time for an article number. |
||
1258 | * |
||
1259 | * @param int $post The article number to get the time from. |
||
1260 | * @param array $groupData Usenet group info from NNTP selectGroup method. |
||
1261 | * |
||
1262 | * @return int Timestamp. |
||
1263 | * @throws \Exception |
||
1264 | */ |
||
1265 | public function postdate($post, array $groupData): int |
||
1266 | { |
||
1267 | // Set table names |
||
1268 | $groupID = Group::getIDByName($groupData['group']); |
||
1269 | $group = []; |
||
1270 | if ($groupID !== '') { |
||
1271 | $group = (new Group())->getCBPTableNames($groupID); |
||
1272 | } |
||
1273 | |||
1274 | $currentPost = $post; |
||
1275 | |||
1276 | $attempts = $date = 0; |
||
1277 | do { |
||
1278 | // Try to get the article date locally first. |
||
1279 | if ($groupID !== '') { |
||
1280 | // Try to get locally. |
||
1281 | $local = $this->_pdo->queryOneRow( |
||
1282 | sprintf( |
||
1283 | ' |
||
1284 | SELECT c.date AS date |
||
1285 | FROM %s c |
||
1286 | INNER JOIN %s b ON(c.id=b.collections_id) |
||
1287 | INNER JOIN %s p ON(b.id=p.binaries_id) |
||
1288 | WHERE p.number = %s', |
||
1289 | $group['cname'], |
||
1290 | $group['bname'], |
||
1291 | $group['pname'], |
||
1292 | $currentPost |
||
1293 | ) |
||
1294 | ); |
||
1295 | if ($local !== false) { |
||
1296 | $date = $local['date']; |
||
1297 | break; |
||
1298 | } |
||
1299 | } |
||
1300 | |||
1301 | // If we could not find it locally, try usenet. |
||
1302 | $header = $this->_nntp->getXOVER($currentPost); |
||
1303 | if (! $this->_nntp->isError($header)) { |
||
1304 | // Check if the date is set. |
||
1305 | if (isset($header[0]['Date']) && \strlen($header[0]['Date']) > 0) { |
||
1306 | $date = $header[0]['Date']; |
||
1307 | break; |
||
1308 | } |
||
1309 | } |
||
1310 | |||
1311 | // Try to get a different article number. |
||
1312 | if (abs($currentPost - $groupData['first']) > abs($groupData['last'] - $currentPost)) { |
||
1313 | $tempPost = round($currentPost / (random_int(1005, 1012) / 1000), 0, PHP_ROUND_HALF_UP); |
||
1314 | if ($tempPost < $groupData['first']) { |
||
1315 | $tempPost = $groupData['first']; |
||
1316 | } |
||
1317 | } else { |
||
1318 | $tempPost = round((random_int(1005, 1012) / 1000) * $currentPost, 0, PHP_ROUND_HALF_UP); |
||
1319 | if ($tempPost > $groupData['last']) { |
||
1320 | $tempPost = $groupData['last']; |
||
1321 | } |
||
1322 | } |
||
1323 | // If we got the same article number as last time, give up. |
||
1324 | if ($tempPost === $currentPost) { |
||
1325 | break; |
||
1326 | } |
||
1327 | $currentPost = $tempPost; |
||
1328 | |||
1329 | if ($this->_debug) { |
||
1330 | ColorCLI::doEcho(ColorCLI::debug('Postdate retried '.$attempts.' time(s).'), true); |
||
1331 | } |
||
1332 | } while ($attempts++ <= 20); |
||
1333 | |||
1334 | // If we didn't get a date, set it to now. |
||
1335 | if (! $date) { |
||
1336 | $date = time(); |
||
1337 | } else { |
||
1338 | $date = strtotime($date); |
||
1339 | } |
||
1340 | |||
1341 | return $date; |
||
1342 | } |
||
1343 | |||
1344 | /** |
||
1345 | * Returns article number based on # of days. |
||
1346 | * |
||
1347 | * @param int $days How many days back we want to go. |
||
1348 | * @param array $data Group data from usenet. |
||
1349 | * |
||
1350 | * @return string |
||
1351 | * @throws \Exception |
||
1352 | */ |
||
1353 | public function daytopost($days, $data): string |
||
1354 | { |
||
1355 | $goalTime = Carbon::now()->subDays($days)->timestamp; |
||
1356 | // The time we want = current unix time (ex. 1395699114) - minus 86400 (seconds in a day) |
||
1357 | // times days wanted. (ie 1395699114 - 2592000 (30days)) = 1393107114 |
||
1358 | |||
1359 | // The servers oldest date. |
||
1360 | $firstDate = $this->postdate($data['first'], $data); |
||
1361 | if ($goalTime < $firstDate) { |
||
1362 | // If the date we want is older than the oldest date in the group return the groups oldest article. |
||
1363 | return $data['first']; |
||
1364 | } |
||
1365 | |||
1366 | // The servers newest date. |
||
1367 | $lastDate = $this->postdate($data['last'], $data); |
||
1368 | if ($goalTime > $lastDate) { |
||
1369 | // If the date we want is newer than the groups newest date, return the groups newest article. |
||
1370 | return $data['last']; |
||
1371 | } |
||
1372 | |||
1373 | if ($this->_echoCLI) { |
||
1374 | ColorCLI::doEcho( |
||
1375 | ColorCLI::primary( |
||
1376 | 'Searching for an approximate article number for group '.$data['group'].' '.$days.' days back.' |
||
1377 | ), true |
||
1378 | ); |
||
1379 | } |
||
1380 | |||
1381 | // Pick the middle to start with |
||
1382 | $wantedArticle = round(($data['last'] + $data['first']) / 2); |
||
1383 | $aMax = $data['last']; |
||
1384 | $aMin = $data['first']; |
||
1385 | $reallyOldArticle = $oldArticle = $articleTime = null; |
||
1386 | |||
1387 | while (true) { |
||
1388 | // Article exists outside of available range, this shouldn't happen |
||
1389 | if ($wantedArticle <= $data['first'] || $wantedArticle >= $data['last']) { |
||
1390 | break; |
||
1391 | } |
||
1392 | |||
1393 | // Keep a note of the last articles we checked |
||
1394 | $reallyOldArticle = $oldArticle; |
||
1395 | $oldArticle = $wantedArticle; |
||
1396 | |||
1397 | // Get the date of this article |
||
1398 | $articleTime = $this->postdate($wantedArticle, $data); |
||
1399 | |||
1400 | // Article doesn't exist, start again with something random |
||
1401 | if (! $articleTime) { |
||
1402 | $wantedArticle = random_int($aMin, $aMax); |
||
1403 | $articleTime = $this->postdate($wantedArticle, $data); |
||
1404 | } |
||
1405 | |||
1406 | if ($articleTime < $goalTime) { |
||
1407 | // Article is older than we want |
||
1408 | $aMin = $oldArticle; |
||
1409 | $wantedArticle = round(($aMax + $oldArticle) / 2); |
||
1410 | if ($this->_echoCLI) { |
||
1411 | echo '-'; |
||
1412 | } |
||
1413 | } elseif ($articleTime > $goalTime) { |
||
1414 | // Article is newer than we want |
||
1415 | $aMax = $oldArticle; |
||
1416 | $wantedArticle = round(($aMin + $oldArticle) / 2); |
||
1417 | if ($this->_echoCLI) { |
||
1418 | echo '+'; |
||
1419 | } |
||
1420 | } elseif ($articleTime === $goalTime) { |
||
1421 | // Exact match. We did it! (this will likely never happen though) |
||
1422 | break; |
||
1423 | } |
||
1424 | |||
1425 | // We seem to be flip-flopping between 2 articles, assume we're out of articles to check. |
||
1426 | // End on an article more recent than our oldest so that we don't miss any releases. |
||
1427 | if ($reallyOldArticle === $wantedArticle && ($goalTime - $articleTime) <= 0) { |
||
1428 | break; |
||
1429 | } |
||
1430 | } |
||
1431 | |||
1432 | $wantedArticle = (int) $wantedArticle; |
||
1433 | if ($this->_echoCLI) { |
||
1434 | ColorCLI::doEcho( |
||
1435 | ColorCLI::primary( |
||
1436 | PHP_EOL.'Found article #'.$wantedArticle.' which has a date of '.date('r', $articleTime). |
||
1437 | ', vs wanted date of '.date('r', $goalTime).'. Difference from goal is '.Carbon::createFromTimestamp($goalTime)->diffInDays(Carbon::createFromTimestamp($articleTime)).'days.' |
||
1438 | ), true |
||
1439 | ); |
||
1440 | } |
||
1441 | |||
1442 | return $wantedArticle; |
||
1443 | } |
||
1444 | |||
1445 | /** |
||
1446 | * Convert unix time to days ago. |
||
1447 | * |
||
1448 | * |
||
1449 | * @param $timestamp |
||
1450 | * @return int |
||
1451 | */ |
||
1452 | private function daysOld($timestamp): int |
||
1453 | { |
||
1454 | return Carbon::createFromTimestamp($timestamp)->diffInDays(); |
||
1455 | } |
||
1456 | |||
1457 | /** |
||
1458 | * Add article numbers from missing headers to DB. |
||
1459 | * |
||
1460 | * @param array $numbers The article numbers of the missing headers. |
||
1461 | * @param string $tableName Name of the partrepair table to insert into. |
||
1462 | * @param int $groupID The ID of this groups. |
||
1463 | * |
||
1464 | * @return bool |
||
1465 | */ |
||
1466 | private function addMissingParts($numbers, $tableName, $groupID): bool |
||
1467 | { |
||
1468 | $insertStr = 'INSERT INTO '.$tableName.' (numberid, groups_id) VALUES '; |
||
1469 | foreach ($numbers as $number) { |
||
1470 | $insertStr .= '('.$number.','.$groupID.'),'; |
||
1471 | } |
||
1472 | |||
1473 | return $this->_pdo->queryInsert(rtrim($insertStr, ',').' ON DUPLICATE KEY UPDATE attempts=attempts+1'); |
||
1474 | } |
||
1475 | |||
1476 | /** |
||
1477 | * Clean up part repair table. |
||
1478 | * |
||
1479 | * @param array $numbers The article numbers. |
||
1480 | * @param string $tableName Name of the part repair table to work on. |
||
1481 | * @param int $groupID The ID of the group. |
||
1482 | * |
||
1483 | * @return void |
||
1484 | */ |
||
1485 | private function removeRepairedParts(array $numbers, $tableName, $groupID): void |
||
1486 | { |
||
1487 | $sql = 'DELETE FROM '.$tableName.' WHERE numberid in ('; |
||
1488 | foreach ($numbers as $number) { |
||
1489 | $sql .= $number.','; |
||
1490 | } |
||
1491 | $this->_pdo->queryExec(rtrim($sql, ',').') AND groups_id = '.$groupID); |
||
1492 | } |
||
1493 | |||
1494 | /** |
||
1495 | * Are white or black lists loaded for a group name? |
||
1496 | * @var array |
||
1497 | */ |
||
1498 | protected $_listsFound = []; |
||
1499 | |||
1500 | /** |
||
1501 | * Get blacklist and cache it. Return if already cached. |
||
1502 | * |
||
1503 | * @param string $groupName |
||
1504 | * |
||
1505 | * @return void |
||
1506 | */ |
||
1507 | protected function _retrieveBlackList($groupName): void |
||
1508 | { |
||
1509 | if (! isset($this->blackList[$groupName])) { |
||
1510 | $this->blackList[$groupName] = $this->getBlacklist(true, self::OPTYPE_BLACKLIST, $groupName, true); |
||
1511 | } |
||
1512 | if (! isset($this->whiteList[$groupName])) { |
||
1513 | $this->whiteList[$groupName] = $this->getBlacklist(true, self::OPTYPE_WHITELIST, $groupName, true); |
||
1514 | } |
||
1515 | $this->_listsFound[$groupName] = ($this->blackList[$groupName] || $this->whiteList[$groupName]); |
||
1516 | } |
||
1517 | |||
1518 | /** |
||
1519 | * Check if an article is blacklisted. |
||
1520 | * |
||
1521 | * @param array $msg The article header (OVER format). |
||
1522 | * @param string $groupName The group name. |
||
1523 | * |
||
1524 | * @return bool |
||
1525 | */ |
||
1526 | public function isBlackListed($msg, $groupName): bool |
||
1527 | { |
||
1528 | if (! isset($this->_listsFound[$groupName])) { |
||
1529 | $this->_retrieveBlackList($groupName); |
||
1530 | } |
||
1531 | if (! $this->_listsFound[$groupName]) { |
||
1532 | return false; |
||
1533 | } |
||
1534 | |||
1535 | $blackListed = false; |
||
1536 | |||
1537 | $field = [ |
||
1538 | self::BLACKLIST_FIELD_SUBJECT => $msg['Subject'], |
||
1539 | self::BLACKLIST_FIELD_FROM => $msg['From'], |
||
1540 | self::BLACKLIST_FIELD_MESSAGEID => $msg['Message-ID'], |
||
1541 | ]; |
||
1542 | |||
1543 | // Try white lists first. |
||
1544 | if ($this->whiteList[$groupName]) { |
||
1545 | // There are white lists for this group, so anything that doesn't match a white list should be considered black listed. |
||
1546 | $blackListed = true; |
||
1547 | foreach ($this->whiteList[$groupName] as $whiteList) { |
||
1548 | if (preg_match('/'.$whiteList['regex'].'/i', $field[$whiteList['msgcol']])) { |
||
1549 | // This field matched a white list, so it might not be black listed. |
||
1550 | $blackListed = false; |
||
1551 | $this->_binaryBlacklistIdsToUpdate[$whiteList['id']] = $whiteList['id']; |
||
1552 | break; |
||
1553 | } |
||
1554 | } |
||
1555 | } |
||
1556 | |||
1557 | // Check if the field is black listed. |
||
1558 | if (! $blackListed && $this->blackList[$groupName]) { |
||
1559 | foreach ($this->blackList[$groupName] as $blackList) { |
||
1560 | if (preg_match('/'.$blackList['regex'].'/i', $field[$blackList['msgcol']])) { |
||
1561 | $blackListed = true; |
||
1562 | $this->_binaryBlacklistIdsToUpdate[$blackList['id']] = $blackList['id']; |
||
1563 | break; |
||
1564 | } |
||
1565 | } |
||
1566 | } |
||
1567 | |||
1568 | return $blackListed; |
||
1569 | } |
||
1570 | |||
1571 | /** |
||
1572 | * Return all blacklists. |
||
1573 | * |
||
1574 | * @param bool $activeOnly Only display active blacklists ? |
||
1575 | * @param int|string $opType Optional, get white or black lists (use Binaries constants). |
||
1576 | * @param string $groupName Optional, group. |
||
1577 | * @param bool $groupRegex Optional Join groups / binaryblacklist using regexp for equals. |
||
1578 | * |
||
1579 | * @return array |
||
1580 | */ |
||
1581 | public function getBlacklist($activeOnly = true, $opType = -1, $groupName = '', $groupRegex = false): array |
||
1582 | { |
||
1583 | switch ($opType) { |
||
1584 | case self::OPTYPE_BLACKLIST: |
||
1585 | $opType = 'AND bb.optype = '.self::OPTYPE_BLACKLIST; |
||
1586 | break; |
||
1587 | case self::OPTYPE_WHITELIST: |
||
1588 | $opType = 'AND bb.optype = '.self::OPTYPE_WHITELIST; |
||
1589 | break; |
||
1590 | default: |
||
1591 | $opType = ''; |
||
1592 | break; |
||
1593 | } |
||
1594 | |||
1595 | return $this->_pdo->query( |
||
1596 | sprintf( |
||
1597 | ' |
||
1598 | SELECT |
||
1599 | bb.id, bb.optype, bb.status, bb.description, |
||
1600 | bb.groupname AS groupname, bb.regex, g.id AS group_id, bb.msgcol, |
||
1601 | bb.last_activity as last_activity |
||
1602 | FROM binaryblacklist bb |
||
1603 | LEFT OUTER JOIN groups g ON g.name %s bb.groupname |
||
1604 | WHERE 1=1 %s %s %s |
||
1605 | ORDER BY coalesce(groupname,\'zzz\')', |
||
1606 | ($groupRegex ? 'REGEXP' : '='), |
||
1607 | ($activeOnly ? 'AND bb.status = 1' : ''), |
||
1608 | $opType, |
||
1609 | ($groupName ? ('AND g.name REGEXP '.$this->_pdo->escapeString($groupName)) : '') |
||
1610 | ) |
||
1611 | ); |
||
1612 | } |
||
1613 | |||
1614 | /** |
||
1615 | * Return the specified blacklist. |
||
1616 | * |
||
1617 | * @param int $id The blacklist ID. |
||
1618 | * |
||
1619 | * @return \Illuminate\Database\Eloquent\Model|null|static |
||
1620 | */ |
||
1621 | public function getBlacklistByID($id) |
||
1624 | } |
||
1625 | |||
1626 | /** |
||
1627 | * Delete a blacklist. |
||
1628 | * |
||
1629 | * @param int $id The ID of the blacklist. |
||
1630 | */ |
||
1631 | public function deleteBlacklist($id): void |
||
1632 | { |
||
1633 | BinaryBlacklist::query()->where('id', $id)->delete(); |
||
1634 | } |
||
1635 | |||
1636 | /** |
||
1637 | * @param $blacklistArray |
||
1638 | */ |
||
1639 | public function updateBlacklist($blacklistArray): void |
||
1640 | { |
||
1641 | BinaryBlacklist::query()->where('id', $blacklistArray['id'])->update( |
||
1642 | [ |
||
1643 | 'groupname' => $blacklistArray['groupname'] === '' ? 'null' : preg_replace('/a\.b\./i', 'alt.binaries.', $blacklistArray['groupname']), |
||
1644 | 'regex' => $blacklistArray['regex'], |
||
1645 | 'status' => $blacklistArray['status'], |
||
1646 | 'description' => $blacklistArray['description'], |
||
1647 | 'optype' => $blacklistArray['optype'], |
||
1648 | 'msgcol' => $blacklistArray['msgcol'], |
||
1649 | ] |
||
1650 | ); |
||
1651 | } |
||
1652 | |||
1653 | /** |
||
1654 | * Adds a new blacklist from binary blacklist edit admin web page. |
||
1655 | * |
||
1656 | * @param array $blacklistArray |
||
1657 | */ |
||
1658 | public function addBlacklist($blacklistArray): void |
||
1659 | { |
||
1660 | BinaryBlacklist::query()->insert( |
||
1661 | [ |
||
1662 | 'groupname' => $blacklistArray['groupname'] === '' ? 'null' : preg_replace('/a\.b\./i', 'alt.binaries.', $blacklistArray['groupname']), |
||
1663 | 'regex' => $blacklistArray['regex'], |
||
1664 | 'status' => $blacklistArray['status'], |
||
1665 | 'description' => $blacklistArray['description'], |
||
1666 | 'optype' => $blacklistArray['optype'], |
||
1667 | 'msgcol' => $blacklistArray['msgcol'], |
||
1668 | ] |
||
1669 | ); |
||
1670 | } |
||
1671 | |||
1672 | /** |
||
1673 | * Delete Collections/Binaries/Parts for a Collection ID. |
||
1674 | * |
||
1675 | * @param int $collectionID Collections table ID |
||
1676 | * |
||
1677 | * @note A trigger automatically deletes the parts/binaries. |
||
1678 | * |
||
1679 | * @return void |
||
1680 | */ |
||
1681 | public function delete($collectionID): void |
||
1682 | { |
||
1683 | $this->_pdo->queryExec(sprintf('DELETE FROM collections WHERE id = %d', $collectionID)); |
||
1684 | } |
||
1685 | |||
1686 | /** |
||
1687 | * Log / Echo message. |
||
1688 | * |
||
1689 | * @param string $message Message to log. |
||
1690 | * @param string $method Method that called this. |
||
1691 | * @param string $color ColorCLI method name. |
||
1692 | */ |
||
1693 | private function log($message, $method, $color): void |
||
1699 | ); |
||
1700 | } |
||
1701 | } |
||
1702 | |||
1703 | /** |
||
1704 | * Check if we should ignore the file count and return true or false. |
||
1705 | * |
||
1706 | * @param string $groupName |
||
1707 | * @param string $subject |
||
1708 | * |
||
1709 | * @return bool |
||
1710 | */ |
||
1711 | protected function _ignoreFileCount($groupName, $subject): bool |
||
1712 | { |
||
1713 | $ignore = false; |
||
1714 | switch ($groupName) { |
||
1715 | case 'alt.binaries.erotica': |
||
1716 | if (preg_match('/^\[\d+\]-\[FULL\]-\[#a\.b\.erotica@EFNet\]-\[ \d{2,3}_/', $subject)) { |
||
1717 | $ignore = true; |
||
1718 | } |
||
1719 | break; |
||
1720 | } |
||
1721 | |||
1722 | return $ignore; |
||
1723 | } |
||
1724 | |||
1725 | /** |
||
1726 | * Returns all multigroup poster entries from the database. |
||
1727 | * |
||
1728 | * |
||
1729 | * @return \Illuminate\Database\Eloquent\Collection|static[] |
||
1730 | */ |
||
1731 | protected function getMultiGroupPosters() |
||
1743 | } |
||
1744 | } |
||
1745 |