Total Complexity | 183 |
Total Lines | 1666 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ProcessReleases 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 ProcessReleases, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class ProcessReleases |
||
25 | { |
||
26 | public const COLLFC_DEFAULT = 0; // Collection has default filecheck status |
||
27 | public const COLLFC_COMPCOLL = 1; // Collection is a complete collection |
||
28 | public const COLLFC_COMPPART = 2; // Collection is a complete collection and has all parts available |
||
29 | public const COLLFC_SIZED = 3; // Collection has been calculated for total size |
||
30 | public const COLLFC_INSERTED = 4; // Collection has been inserted into releases |
||
31 | public const COLLFC_DELETE = 5; // Collection is ready for deletion |
||
32 | public const COLLFC_TEMPCOMP = 15; // Collection is complete and being checked for complete parts |
||
33 | public const COLLFC_ZEROPART = 16; // Collection has a 00/0XX designator (temporary) |
||
34 | |||
35 | public const FILE_INCOMPLETE = 0; // We don't have all the parts yet for the file (binaries table partcheck column). |
||
36 | public const FILE_COMPLETE = 1; // We have all the parts for the file (binaries table partcheck column). |
||
37 | |||
38 | /** |
||
39 | * @var int |
||
40 | */ |
||
41 | public $collectionDelayTime; |
||
42 | |||
43 | /** |
||
44 | * @var int |
||
45 | */ |
||
46 | public $crossPostTime; |
||
47 | |||
48 | /** |
||
49 | * @var int |
||
50 | */ |
||
51 | public $releaseCreationLimit; |
||
52 | |||
53 | /** |
||
54 | * @var int |
||
55 | */ |
||
56 | public $completion; |
||
57 | |||
58 | /** |
||
59 | * @var bool |
||
60 | */ |
||
61 | public $echoCLI; |
||
62 | |||
63 | /** |
||
64 | * @var \Blacklight\db\DB |
||
65 | */ |
||
66 | public $pdo; |
||
67 | |||
68 | /** |
||
69 | * @var \Blacklight\ConsoleTools |
||
70 | */ |
||
71 | public $consoleTools; |
||
72 | |||
73 | /** |
||
74 | * @var \Blacklight\NZB |
||
75 | */ |
||
76 | public $nzb; |
||
77 | |||
78 | /** |
||
79 | * @var \Blacklight\ReleaseCleaning |
||
80 | */ |
||
81 | public $releaseCleaning; |
||
82 | |||
83 | /** |
||
84 | * @var \Blacklight\Releases |
||
85 | */ |
||
86 | public $releases; |
||
87 | |||
88 | /** |
||
89 | * @var \Blacklight\ReleaseImage |
||
90 | */ |
||
91 | public $releaseImage; |
||
92 | |||
93 | /** |
||
94 | * List of table names to be using for method calls. |
||
95 | * |
||
96 | * |
||
97 | * @var array |
||
98 | */ |
||
99 | protected $tables = []; |
||
100 | |||
101 | /** |
||
102 | * @var string |
||
103 | */ |
||
104 | protected $fromNamesQuery; |
||
105 | |||
106 | /** |
||
107 | * Time (hours) to wait before delete a stuck/broken collection. |
||
108 | * |
||
109 | * |
||
110 | * @var int |
||
111 | */ |
||
112 | private $collectionTimeout; |
||
113 | |||
114 | /** |
||
115 | * @param array $options Class instances / Echo to cli ? |
||
116 | * |
||
117 | * @throws \Exception |
||
118 | */ |
||
119 | public function __construct(array $options = []) |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * Main method for creating releases/NZB files from collections. |
||
159 | * |
||
160 | * @param int $categorize |
||
161 | * @param int $postProcess |
||
162 | * @param string $groupName (optional) |
||
163 | * @param \Blacklight\NNTP $nntp |
||
164 | * @param bool $echooutput |
||
165 | * |
||
166 | * @return int |
||
167 | * @throws \Exception |
||
168 | */ |
||
169 | public function processReleases($categorize, $postProcess, $groupName, &$nntp, $echooutput): int |
||
170 | { |
||
171 | $this->echoCLI = ($echooutput && config('nntmux.echocli')); |
||
172 | $groupID = ''; |
||
173 | |||
174 | if (! empty($groupName) && $groupName !== 'mgr') { |
||
175 | $groupInfo = Group::getByName($groupName); |
||
176 | if ($groupInfo !== null) { |
||
177 | $groupID = $groupInfo['id']; |
||
178 | } |
||
179 | } |
||
180 | |||
181 | if ($this->echoCLI) { |
||
182 | ColorCLI::doEcho(ColorCLI::header('Starting release update process ('.date('Y-m-d H:i:s').')'), true); |
||
183 | } |
||
184 | |||
185 | if (! file_exists(Settings::settingValue('..nzbpath'))) { |
||
186 | if ($this->echoCLI) { |
||
187 | ColorCLI::doEcho( |
||
188 | ColorCLI::error('Bad or missing nzb directory - '.Settings::settingValue('..nzbpath')), |
||
189 | true |
||
190 | ); |
||
191 | } |
||
192 | |||
193 | return 0; |
||
194 | } |
||
195 | |||
196 | $this->processIncompleteCollections($groupID); |
||
197 | $this->processCollectionSizes($groupID); |
||
198 | $this->deleteUnwantedCollections($groupID); |
||
199 | |||
200 | $totalReleasesAdded = 0; |
||
201 | do { |
||
202 | $releasesCount = $this->createReleases($groupID); |
||
203 | $totalReleasesAdded += $releasesCount['added']; |
||
204 | |||
205 | $nzbFilesAdded = $this->createNZBs($groupID); |
||
206 | |||
207 | $this->categorizeReleases($categorize, $groupID); |
||
208 | $this->postProcessReleases($postProcess, $nntp); |
||
209 | $this->deleteCollections($groupID); |
||
210 | |||
211 | // This loops as long as the number of releases or nzbs added was >= the limit (meaning there are more waiting to be created) |
||
212 | } while ( |
||
213 | ($releasesCount['added'] + $releasesCount['dupes']) >= $this->releaseCreationLimit |
||
214 | || $nzbFilesAdded >= $this->releaseCreationLimit |
||
215 | ); |
||
216 | |||
217 | // Only run if non-mgr as mgr is not specific to group |
||
218 | if ($groupName !== 'mgr') { |
||
219 | $this->deletedReleasesByGroup($groupID); |
||
220 | $this->deleteReleases(); |
||
221 | } |
||
222 | |||
223 | return $totalReleasesAdded; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Return all releases to other->misc category. |
||
228 | * |
||
229 | * @param string $where Optional "where" query parameter. |
||
230 | * |
||
231 | * @void |
||
232 | */ |
||
233 | public function resetCategorize($where = ''): void |
||
234 | { |
||
235 | $this->pdo->queryExec( |
||
236 | sprintf('UPDATE releases SET categories_id = %d, iscategorized = 0 %s', Category::OTHER_MISC, $where) |
||
237 | ); |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Categorizes releases. |
||
242 | * |
||
243 | * @param string $type name or searchname | Categorize using the search name or subject. |
||
244 | * @param string $where Optional "where" query parameter. |
||
245 | * |
||
246 | * @return int Quantity of categorized releases. |
||
247 | * @throws \Exception |
||
248 | */ |
||
249 | public function categorizeRelease($type, $where = ''): int |
||
250 | { |
||
251 | $cat = new Categorize(['Settings' => $this->pdo]); |
||
252 | $categorized = $total = 0; |
||
253 | $releases = $this->pdo->queryDirect( |
||
254 | sprintf( |
||
255 | ' |
||
256 | SELECT id, fromname, %s, groups_id |
||
257 | FROM releases %s', |
||
258 | $type, |
||
259 | $where |
||
260 | ) |
||
261 | ); |
||
262 | if ($releases && $releases->rowCount()) { |
||
263 | $total = $releases->rowCount(); |
||
264 | foreach ($releases as $release) { |
||
265 | $catId = $cat->determineCategory($release['groups_id'], $release[$type], $release['fromname']); |
||
266 | Release::query()->where('id', $release['id'])->update(['categories_id' => $catId, 'iscategorized' => 1]); |
||
267 | $categorized++; |
||
268 | if ($this->echoCLI) { |
||
269 | $this->consoleTools->overWritePrimary( |
||
270 | 'Categorizing: '.$this->consoleTools->percentString($categorized, $total) |
||
271 | ); |
||
272 | } |
||
273 | } |
||
274 | } |
||
275 | if ($this->echoCLI !== false && $categorized > 0) { |
||
276 | echo PHP_EOL; |
||
277 | } |
||
278 | |||
279 | return $categorized; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * @param $groupID |
||
284 | * @throws \Exception |
||
285 | */ |
||
286 | public function processIncompleteCollections($groupID): void |
||
287 | { |
||
288 | $startTime = time(); |
||
289 | $this->initiateTableNames($groupID); |
||
290 | |||
291 | if ($this->echoCLI) { |
||
292 | ColorCLI::doEcho(ColorCLI::header('Process Releases -> Attempting to find complete collections.')); |
||
293 | } |
||
294 | |||
295 | $where = (! empty($groupID) ? ' AND c.groups_id = '.$groupID.' ' : ' '); |
||
296 | |||
297 | $this->processStuckCollections($where); |
||
298 | $this->collectionFileCheckStage1($where); |
||
299 | $this->collectionFileCheckStage2($where); |
||
300 | $this->collectionFileCheckStage3($where); |
||
301 | $this->collectionFileCheckStage4($where); |
||
302 | $this->collectionFileCheckStage5($where); |
||
303 | $this->collectionFileCheckStage6($where); |
||
304 | |||
305 | if ($this->echoCLI) { |
||
306 | $count = $this->pdo->queryOneRow( |
||
307 | sprintf( |
||
308 | ' |
||
309 | SELECT COUNT(c.id) AS complete |
||
310 | FROM %s c |
||
311 | WHERE c.filecheck = %d %s', |
||
312 | $this->tables['cname'], |
||
313 | self::COLLFC_COMPPART, |
||
314 | $where |
||
315 | ) |
||
316 | ); |
||
317 | ColorCLI::doEcho( |
||
318 | ColorCLI::primary( |
||
319 | ($count === false ? 0 : $count['complete']).' collections were found to be complete. Time: '. |
||
320 | $this->consoleTools->convertTime(time() - $startTime) |
||
321 | ), |
||
322 | true |
||
323 | ); |
||
324 | } |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * @param $groupID |
||
329 | */ |
||
330 | public function processCollectionSizes($groupID): void |
||
331 | { |
||
332 | $startTime = time(); |
||
333 | $this->initiateTableNames($groupID); |
||
334 | |||
335 | if ($this->echoCLI) { |
||
336 | ColorCLI::doEcho(ColorCLI::header('Process Releases -> Calculating collection sizes (in bytes).'), true); |
||
337 | } |
||
338 | // Get the total size in bytes of the collection for collections where filecheck = 2. |
||
339 | $checked = $this->pdo->queryExec( |
||
340 | sprintf( |
||
341 | ' |
||
342 | UPDATE %s c |
||
343 | SET c.filesize = |
||
344 | ( |
||
345 | SELECT COALESCE(SUM(b.partsize), 0) |
||
346 | FROM %s b |
||
347 | WHERE b.collections_id = c.id |
||
348 | ), |
||
349 | c.filecheck = %d |
||
350 | WHERE c.filecheck = %d |
||
351 | AND c.filesize = 0 %s', |
||
352 | $this->tables['cname'], |
||
353 | $this->tables['bname'], |
||
354 | self::COLLFC_SIZED, |
||
355 | self::COLLFC_COMPPART, |
||
356 | (! empty($groupID) ? ' AND c.groups_id = '.$groupID : ' ') |
||
357 | ) |
||
358 | ); |
||
359 | if ($checked !== false && $this->echoCLI) { |
||
360 | ColorCLI::doEcho( |
||
361 | ColorCLI::primary( |
||
362 | $checked->rowCount().' collections set to filecheck = 3(size calculated)' |
||
363 | ), true |
||
364 | ); |
||
365 | ColorCLI::doEcho(ColorCLI::primary($this->consoleTools->convertTime(time() - $startTime)), true); |
||
366 | } |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * @param $groupID |
||
371 | * |
||
372 | * @throws \Exception |
||
373 | */ |
||
374 | public function deleteUnwantedCollections($groupID): void |
||
375 | { |
||
376 | $startTime = time(); |
||
377 | $this->initiateTableNames($groupID); |
||
378 | |||
379 | if ($this->echoCLI) { |
||
380 | ColorCLI::doEcho( |
||
381 | ColorCLI::header( |
||
382 | 'Process Releases -> Delete collections smaller/larger than minimum size/file count from group/site setting.' |
||
383 | ), true |
||
384 | ); |
||
385 | } |
||
386 | |||
387 | $groupID === '' ? $groupIDs = Group::getActiveIDs() : $groupIDs = [['id' => $groupID]]; |
||
388 | |||
389 | $minSizeDeleted = $maxSizeDeleted = $minFilesDeleted = 0; |
||
390 | |||
391 | $maxSizeSetting = Settings::settingValue('.release.maxsizetoformrelease'); |
||
392 | $minSizeSetting = Settings::settingValue('.release.minsizetoformrelease'); |
||
393 | $minFilesSetting = Settings::settingValue('.release.minfilestoformrelease'); |
||
394 | |||
395 | foreach ($groupIDs as $grpID) { |
||
396 | $groupMinSizeSetting = $groupMinFilesSetting = 0; |
||
397 | |||
398 | $groupMinimums = Group::getGroupByID($grpID['id']); |
||
399 | if ($groupMinimums !== null) { |
||
400 | if (! empty($groupMinimums['minsizetoformrelease']) && $groupMinimums['minsizetoformrelease'] > 0) { |
||
401 | $groupMinSizeSetting = (int) $groupMinimums['minsizetoformrelease']; |
||
402 | } |
||
403 | if (! empty($groupMinimums['minfilestoformrelease']) && $groupMinimums['minfilestoformrelease'] > 0) { |
||
404 | $groupMinFilesSetting = (int) $groupMinimums['minfilestoformrelease']; |
||
405 | } |
||
406 | } |
||
407 | |||
408 | if ($this->pdo->queryOneRow( |
||
409 | sprintf( |
||
410 | ' |
||
411 | SELECT SQL_NO_CACHE id |
||
412 | FROM %s c |
||
413 | WHERE c.filecheck = %d |
||
414 | AND c.filesize > 0', |
||
415 | $this->tables['cname'], |
||
416 | self::COLLFC_SIZED |
||
417 | ) |
||
418 | ) !== false |
||
419 | ) { |
||
420 | $deleteQuery = $this->pdo->queryExec( |
||
421 | sprintf( |
||
422 | ' |
||
423 | DELETE c FROM %s c |
||
424 | WHERE c.filecheck = %d |
||
425 | AND c.filesize > 0 |
||
426 | AND GREATEST(%d, %d) > 0 |
||
427 | AND c.filesize < GREATEST(%d, %d)', |
||
428 | $this->tables['cname'], |
||
429 | self::COLLFC_SIZED, |
||
430 | $groupMinSizeSetting, |
||
431 | $minSizeSetting, |
||
432 | $groupMinSizeSetting, |
||
433 | $minSizeSetting |
||
434 | ) |
||
435 | ); |
||
436 | if ($deleteQuery !== false) { |
||
437 | $minSizeDeleted += $deleteQuery->rowCount(); |
||
438 | } |
||
439 | |||
440 | if ($maxSizeSetting > 0) { |
||
441 | $deleteQuery = $this->pdo->queryExec( |
||
442 | sprintf( |
||
443 | ' |
||
444 | DELETE c FROM %s c |
||
445 | WHERE c.filecheck = %d |
||
446 | AND c.filesize > %d', |
||
447 | $this->tables['cname'], |
||
448 | self::COLLFC_SIZED, |
||
449 | $maxSizeSetting |
||
450 | ) |
||
451 | ); |
||
452 | if ($deleteQuery !== false) { |
||
453 | $maxSizeDeleted += $deleteQuery->rowCount(); |
||
454 | } |
||
455 | } |
||
456 | |||
457 | if ($minFilesSetting > 0 || $groupMinFilesSetting > 0) { |
||
458 | $deleteQuery = $this->pdo->queryExec( |
||
459 | sprintf( |
||
460 | ' |
||
461 | DELETE c FROM %s c |
||
462 | WHERE c.filecheck = %d |
||
463 | AND GREATEST(%d, %d) > 0 |
||
464 | AND c.totalfiles < GREATEST(%d, %d)', |
||
465 | $this->tables['cname'], |
||
466 | self::COLLFC_SIZED, |
||
467 | $groupMinFilesSetting, |
||
468 | $minFilesSetting, |
||
469 | $groupMinFilesSetting, |
||
470 | $minFilesSetting |
||
471 | ) |
||
472 | ); |
||
473 | if ($deleteQuery !== false) { |
||
474 | $minFilesDeleted += $deleteQuery->rowCount(); |
||
475 | } |
||
476 | } |
||
477 | } |
||
478 | } |
||
479 | |||
480 | if ($this->echoCLI) { |
||
481 | ColorCLI::doEcho( |
||
482 | ColorCLI::primary( |
||
483 | 'Deleted '.($minSizeDeleted + $maxSizeDeleted + $minFilesDeleted).' collections: '.PHP_EOL. |
||
484 | $minSizeDeleted.' smaller than, '. |
||
485 | $maxSizeDeleted.' bigger than, '. |
||
486 | $minFilesDeleted.' with less files than site/group settings in: '. |
||
487 | $this->consoleTools->convertTime(time() - $startTime) |
||
488 | ), |
||
489 | true |
||
490 | ); |
||
491 | } |
||
492 | } |
||
493 | |||
494 | /** |
||
495 | * @param $groupID |
||
496 | * @throws \Exception |
||
497 | */ |
||
498 | protected function initiateTableNames($groupID): void |
||
499 | { |
||
500 | $this->tables = (new Group())->getCBPTableNames($groupID); |
||
501 | } |
||
502 | |||
503 | /** |
||
504 | * Form fromNamesQuery for creating NZBs. |
||
505 | * |
||
506 | * @void |
||
507 | */ |
||
508 | protected function formFromNamesQuery(): void |
||
509 | { |
||
510 | $posters = MultigroupPoster::commaSeparatedList(); |
||
511 | $this->fromNamesQuery = sprintf("AND r.fromname NOT IN('%s')", $posters); |
||
512 | } |
||
513 | |||
514 | /** |
||
515 | * @param int|string $groupID (optional) |
||
516 | * |
||
517 | * @return array |
||
518 | * @throws \Exception |
||
519 | */ |
||
520 | public function createReleases($groupID): array |
||
521 | { |
||
522 | $startTime = time(); |
||
523 | $this->initiateTableNames($groupID); |
||
524 | |||
525 | $categorize = new Categorize(['Settings' => $this->pdo]); |
||
526 | $returnCount = $duplicate = 0; |
||
527 | |||
528 | if ($this->echoCLI) { |
||
529 | ColorCLI::doEcho(ColorCLI::header('Process Releases -> Create releases from complete collections.'), true); |
||
530 | } |
||
531 | |||
532 | $this->pdo->ping(true); |
||
533 | |||
534 | $collections = $this->pdo->queryDirect( |
||
535 | sprintf( |
||
536 | ' |
||
537 | SELECT SQL_NO_CACHE c.*, g.name AS gname |
||
538 | FROM %s c |
||
539 | INNER JOIN groups g ON c.groups_id = g.id |
||
540 | WHERE %s c.filecheck = %d |
||
541 | AND c.filesize > 0 |
||
542 | LIMIT %d', |
||
543 | $this->tables['cname'], |
||
544 | (! empty($groupID) ? ' c.groups_id = '.$groupID.' AND ' : ' '), |
||
545 | self::COLLFC_SIZED, |
||
546 | $this->releaseCreationLimit |
||
547 | ) |
||
548 | ); |
||
549 | |||
550 | if ($this->echoCLI && $collections !== false) { |
||
551 | echo ColorCLI::primary($collections->rowCount().' Collections ready to be converted to releases.'); |
||
552 | } |
||
553 | |||
554 | if ($collections instanceof \Traversable) { |
||
555 | foreach ($collections as $collection) { |
||
556 | $cleanRelName = utf8_encode(str_replace(['#', '@', '$', '%', '^', '§', '¨', '©', 'Ö'], '', $collection['subject'])); |
||
557 | $fromName = utf8_encode( |
||
558 | trim($collection['fromname'], "'") |
||
559 | ); |
||
560 | |||
561 | // Look for duplicates, duplicates match on releases.name, releases.fromname and releases.size |
||
562 | // A 1% variance in size is considered the same size when the subject and poster are the same |
||
563 | $dupeCheck = Release::query() |
||
564 | ->where(['name' => $cleanRelName, 'fromname' => $fromName]) |
||
565 | ->whereBetween('size', [$collection['filesize'] * .99, $collection['filesize'] * 1.01]) |
||
566 | ->first(['id']); |
||
567 | |||
568 | if ($dupeCheck === null) { |
||
569 | $cleanedName = $this->releaseCleaning->releaseCleaner( |
||
570 | $collection['subject'], |
||
571 | $collection['fromname'], |
||
572 | $collection['filesize'], |
||
573 | $collection['gname'] |
||
574 | ); |
||
575 | |||
576 | if (\is_array($cleanedName)) { |
||
577 | $properName = $cleanedName['properlynamed']; |
||
578 | $preID = $cleanerName['predb'] ?? false; |
||
579 | $cleanedName = $cleanedName['cleansubject']; |
||
580 | } else { |
||
581 | $properName = true; |
||
582 | $preID = false; |
||
583 | } |
||
584 | |||
585 | if ($preID === false && $cleanedName !== '') { |
||
586 | // try to match the cleaned searchname to predb title or filename here |
||
587 | $preMatch = Predb::matchPre($cleanedName); |
||
588 | if ($preMatch !== false) { |
||
589 | $cleanedName = $preMatch['title']; |
||
590 | $preID = $preMatch['predb_id']; |
||
591 | $properName = true; |
||
592 | } |
||
593 | } |
||
594 | |||
595 | $releaseID = Release::insertRelease( |
||
596 | [ |
||
597 | 'name' => $cleanRelName, |
||
598 | 'searchname' => utf8_encode($cleanedName), |
||
599 | 'totalpart' => $collection['totalfiles'], |
||
600 | 'groups_id' => $collection['groups_id'], |
||
601 | 'guid' => createGUID(), |
||
602 | 'postdate' => $collection['date'], |
||
603 | 'fromname' => $fromName, |
||
604 | 'size' => $collection['filesize'], |
||
605 | 'categories_id' => $categorize->determineCategory($collection['groups_id'], $cleanedName), |
||
606 | 'isrenamed' => $properName === true ? 1 : 0, |
||
607 | 'predb_id' => $preID === false ? 0 : $preID, |
||
608 | 'nzbstatus' => NZB::NZB_NONE, |
||
609 | ] |
||
610 | ); |
||
611 | |||
612 | if ($releaseID !== false) { |
||
613 | // Update collections table to say we inserted the release. |
||
614 | $this->pdo->queryExec( |
||
615 | sprintf( |
||
616 | ' |
||
617 | UPDATE %s |
||
618 | SET filecheck = %d, releases_id = %d |
||
619 | WHERE id = %d', |
||
620 | $this->tables['cname'], |
||
621 | self::COLLFC_INSERTED, |
||
622 | $releaseID, |
||
623 | $collection['id'] |
||
624 | ) |
||
625 | ); |
||
626 | |||
627 | // Add the id of regex that matched the collection and release name to release_regexes table |
||
628 | ReleaseRegex::insertIgnore([ |
||
629 | 'releases_id' => $releaseID, |
||
630 | 'collection_regex_id' => $collection['collection_regexes_id'], |
||
631 | 'naming_regex_id' => $cleanedName['id'] ?? 0, |
||
632 | ]); |
||
633 | |||
634 | if (preg_match_all('#(\S+):\S+#', $collection['xref'], $matches)) { |
||
635 | foreach ($matches[1] as $grp) { |
||
636 | //check if the group name is in a valid format |
||
637 | $grpTmp = Group::isValidGroup($grp); |
||
638 | if ($grpTmp !== false) { |
||
639 | //check if the group already exists in database |
||
640 | $xrefGrpID = Group::getIDByName($grpTmp); |
||
641 | if ($xrefGrpID === '') { |
||
642 | $xrefGrpID = Group::addGroup( |
||
643 | [ |
||
644 | 'name' => $grpTmp, |
||
645 | 'description' => 'Added by Release processing', |
||
646 | 'backfill_target' => 1, |
||
647 | 'first_record' => 0, |
||
648 | 'last_record' => 0, |
||
649 | 'active' => 0, |
||
650 | 'backfill' => 0, |
||
651 | 'minfilestoformrelease' => '', |
||
652 | 'minsizetoformrelease' => '', |
||
653 | ] |
||
654 | ); |
||
655 | } |
||
656 | |||
657 | $relGroupsChk = ReleasesGroups::query()->where( |
||
658 | [ |
||
659 | ['releases_id', '=', $releaseID], |
||
660 | ['groups_id', '=', $xrefGrpID], |
||
661 | ] |
||
662 | )->first(); |
||
663 | |||
664 | if ($relGroupsChk === null) { |
||
665 | ReleasesGroups::query()->insert( |
||
666 | [ |
||
667 | 'releases_id' => $releaseID, |
||
668 | 'groups_id' => $xrefGrpID, |
||
669 | ] |
||
670 | ); |
||
671 | } |
||
672 | } |
||
673 | } |
||
674 | } |
||
675 | |||
676 | $returnCount++; |
||
677 | |||
678 | if ($this->echoCLI) { |
||
679 | echo "Added $returnCount releases.\r"; |
||
680 | } |
||
681 | } |
||
682 | } else { |
||
683 | // The release was already in the DB, so delete the collection. |
||
684 | $this->pdo->queryExec( |
||
685 | sprintf( |
||
686 | ' |
||
687 | DELETE c |
||
688 | FROM %s c |
||
689 | WHERE c.collectionhash = %s', |
||
690 | $this->tables['cname'], |
||
691 | $this->pdo->escapeString($collection['collectionhash']) |
||
692 | ) |
||
693 | ); |
||
694 | $duplicate++; |
||
695 | } |
||
696 | } |
||
697 | } |
||
698 | |||
699 | if ($this->echoCLI) { |
||
700 | ColorCLI::doEcho( |
||
701 | ColorCLI::primary( |
||
702 | PHP_EOL. |
||
703 | number_format($returnCount). |
||
704 | ' Releases added and '. |
||
705 | number_format($duplicate). |
||
706 | ' duplicate collections deleted in '. |
||
707 | $this->consoleTools->convertTime(time() - $startTime) |
||
708 | ), |
||
709 | true |
||
710 | ); |
||
711 | } |
||
712 | |||
713 | return ['added' => $returnCount, 'dupes' => $duplicate]; |
||
714 | } |
||
715 | |||
716 | /** |
||
717 | * Create NZB files from complete releases. |
||
718 | * |
||
719 | * @param int|string $groupID (optional) |
||
720 | * |
||
721 | * @return int |
||
722 | * @throws \RuntimeException |
||
723 | */ |
||
724 | public function createNZBs($groupID): int |
||
725 | { |
||
726 | $startTime = time(); |
||
727 | $this->formFromNamesQuery(); |
||
728 | |||
729 | if ($this->echoCLI) { |
||
730 | ColorCLI::doEcho(ColorCLI::header('Process Releases -> Create the NZB, delete collections/binaries/parts.'), true); |
||
731 | } |
||
732 | |||
733 | $releases = $this->pdo->queryDirect( |
||
734 | sprintf( |
||
735 | " |
||
736 | SELECT SQL_NO_CACHE |
||
737 | CONCAT(COALESCE(cp.title,'') , CASE WHEN cp.title IS NULL THEN '' ELSE ' > ' END , c.title) AS title, |
||
738 | r.name, r.id, r.guid |
||
739 | FROM releases r |
||
740 | INNER JOIN categories c ON r.categories_id = c.id |
||
741 | INNER JOIN categories cp ON cp.id = c.parentid |
||
742 | WHERE %s nzbstatus = 0 %s", |
||
743 | (! empty($groupID) ? ' r.groups_id = '.$groupID.' AND ' : ' '), |
||
744 | $this->fromNamesQuery |
||
745 | ) |
||
746 | ); |
||
747 | |||
748 | $nzbCount = 0; |
||
749 | |||
750 | if ($releases && $releases->rowCount()) { |
||
751 | $total = $releases->rowCount(); |
||
752 | // Init vars for writing the NZB's. |
||
753 | $this->nzb->initiateForWrite($groupID); |
||
754 | foreach ($releases as $release) { |
||
755 | if ($this->nzb->writeNZBforReleaseId($release['id'], $release['guid'], $release['name'], $release['title']) === true) { |
||
756 | $nzbCount++; |
||
757 | if ($this->echoCLI) { |
||
758 | echo ColorCLI::primaryOver("Creating NZBs and deleting Collections:\t".$nzbCount.'/'.$total."\r"); |
||
759 | } |
||
760 | } |
||
761 | } |
||
762 | } |
||
763 | |||
764 | $totalTime = (time() - $startTime); |
||
765 | |||
766 | if ($this->echoCLI) { |
||
767 | ColorCLI::doEcho( |
||
768 | ColorCLI::primary( |
||
769 | number_format($nzbCount).' NZBs created/Collections deleted in '. |
||
770 | $totalTime.' seconds.'.PHP_EOL. |
||
771 | 'Total time: '.ColorCLI::primary($this->consoleTools->convertTime($totalTime)).PHP_EOL |
||
772 | ), true |
||
773 | ); |
||
774 | } |
||
775 | |||
776 | return $nzbCount; |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * Categorize releases. |
||
781 | * |
||
782 | * @param int $categorize |
||
783 | * @param int|string $groupID (optional) |
||
784 | * |
||
785 | * @void |
||
786 | * @throws \Exception |
||
787 | */ |
||
788 | public function categorizeReleases($categorize, $groupID = ''): void |
||
789 | { |
||
790 | $startTime = time(); |
||
791 | if ($this->echoCLI) { |
||
792 | echo ColorCLI::header('Process Releases -> Categorize releases.'); |
||
793 | } |
||
794 | switch ((int) $categorize) { |
||
795 | case 2: |
||
796 | $type = 'searchname'; |
||
797 | break; |
||
798 | case 1: |
||
799 | default: |
||
800 | |||
801 | $type = 'name'; |
||
802 | break; |
||
803 | } |
||
804 | $this->categorizeRelease( |
||
805 | $type, |
||
806 | (! empty($groupID) |
||
807 | ? 'WHERE categories_id = '.Category::OTHER_MISC.' AND iscategorized = 0 AND groups_id = '.$groupID |
||
808 | : 'WHERE categories_id = '.Category::OTHER_MISC.' AND iscategorized = 0') |
||
809 | ); |
||
810 | |||
811 | if ($this->echoCLI) { |
||
812 | ColorCLI::doEcho(ColorCLI::primary($this->consoleTools->convertTime(time() - $startTime)), true); |
||
813 | } |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * Post-process releases. |
||
818 | * |
||
819 | * @param int $postProcess |
||
820 | * @param NNTP $nntp |
||
821 | * |
||
822 | * @void |
||
823 | * @throws \Exception |
||
824 | */ |
||
825 | public function postProcessReleases($postProcess, &$nntp): void |
||
826 | { |
||
827 | if ((int) $postProcess === 1) { |
||
828 | (new PostProcess(['Echo' => $this->echoCLI, 'Settings' => $this->pdo, 'Groups' => $this->groups]))->processAll($nntp); |
||
829 | } else { |
||
830 | if ($this->echoCLI) { |
||
831 | ColorCLI::doEcho( |
||
832 | ColorCLI::info( |
||
833 | "\nPost-processing is not running inside the Process Releases class.\n". |
||
834 | 'If you are using tmux or screen they might have their own scripts running Post-processing.' |
||
835 | ), true |
||
836 | ); |
||
837 | } |
||
838 | } |
||
839 | } |
||
840 | |||
841 | /** |
||
842 | * @param $groupID |
||
843 | * |
||
844 | * @throws \Exception |
||
845 | */ |
||
846 | public function deleteCollections($groupID): void |
||
847 | { |
||
848 | $startTime = time(); |
||
849 | $this->initiateTableNames($groupID); |
||
850 | |||
851 | $deletedCount = 0; |
||
852 | |||
853 | // CBP older than retention. |
||
854 | if ($this->echoCLI) { |
||
855 | echo |
||
856 | ColorCLI::header('Process Releases -> Delete finished collections.'.PHP_EOL). |
||
857 | ColorCLI::primary(sprintf( |
||
858 | 'Deleting collections/binaries/parts older than %d hours.', |
||
859 | Settings::settingValue('..partretentionhours') |
||
860 | )); |
||
861 | } |
||
862 | |||
863 | $deleted = 0; |
||
864 | $deleteQuery = $this->pdo->queryExec( |
||
865 | sprintf( |
||
866 | ' |
||
867 | DELETE c |
||
868 | FROM %s c |
||
869 | WHERE (c.dateadded < NOW() - INTERVAL %d HOUR)', |
||
870 | $this->tables['cname'], |
||
871 | Settings::settingValue('..partretentionhours') |
||
872 | ) |
||
873 | ); |
||
874 | |||
875 | if ($deleteQuery !== false) { |
||
876 | $deleted = $deleteQuery->rowCount(); |
||
877 | $deletedCount += $deleted; |
||
878 | } |
||
879 | |||
880 | $firstQuery = $fourthQuery = time(); |
||
881 | |||
882 | if ($this->echoCLI) { |
||
883 | echo ColorCLI::primary( |
||
884 | 'Finished deleting '.$deleted.' old collections/binaries/parts in '. |
||
885 | ($firstQuery - $startTime).' seconds.'.PHP_EOL |
||
886 | ); |
||
887 | } |
||
888 | |||
889 | // Cleanup orphaned collections, binaries and parts |
||
890 | // this really shouldn't happen, but just incase - so we only run 1/200 of the time |
||
891 | if (random_int(0, 200) <= 1) { |
||
892 | // CBP collection orphaned with no binaries or parts. |
||
893 | if ($this->echoCLI) { |
||
894 | echo |
||
895 | ColorCLI::header('Process Releases -> Remove CBP orphans.'.PHP_EOL). |
||
896 | ColorCLI::primary('Deleting orphaned collections.'); |
||
897 | } |
||
898 | |||
899 | $deleted = 0; |
||
900 | $deleteQuery = $this->pdo->queryExec( |
||
901 | sprintf( |
||
902 | ' |
||
903 | DELETE c, b, p |
||
904 | FROM %s c |
||
905 | LEFT JOIN %s b ON c.id = b.collections_id |
||
906 | LEFT JOIN %s p ON b.id = p.binaries_id |
||
907 | WHERE (b.id IS NULL OR p.binaries_id IS NULL)', |
||
908 | $this->tables['cname'], |
||
909 | $this->tables['bname'], |
||
910 | $this->tables['pname'] |
||
911 | ) |
||
912 | ); |
||
913 | |||
914 | if ($deleteQuery !== false) { |
||
915 | $deleted = $deleteQuery->rowCount(); |
||
916 | $deletedCount += $deleted; |
||
917 | } |
||
918 | |||
919 | $secondQuery = time(); |
||
920 | |||
921 | if ($this->echoCLI) { |
||
922 | echo ColorCLI::primary( |
||
923 | 'Finished deleting '.$deleted.' orphaned collections in '. |
||
924 | ($secondQuery - $firstQuery).' seconds.'.PHP_EOL |
||
925 | ); |
||
926 | } |
||
927 | |||
928 | // orphaned binaries - binaries with no parts or binaries with no collection |
||
929 | // Don't delete currently inserting binaries by checking the max id. |
||
930 | if ($this->echoCLI) { |
||
931 | echo ColorCLI::primary('Deleting orphaned binaries/parts with no collection.'); |
||
932 | } |
||
933 | |||
934 | $deleted = 0; |
||
935 | $deleteQuery = $this->pdo->queryExec( |
||
936 | sprintf( |
||
937 | 'DELETE b, p FROM %s b |
||
938 | LEFT JOIN %s p ON b.id = p.binaries_id |
||
939 | LEFT JOIN %s c ON b.collections_id = c.id |
||
940 | WHERE (p.binaries_id IS NULL OR c.id IS NULL) |
||
941 | AND b.id < %d', |
||
942 | $this->tables['bname'], |
||
943 | $this->tables['pname'], |
||
944 | $this->tables['cname'], |
||
945 | $this->maxQueryFormulator($this->tables['bname'], 20000) |
||
946 | ) |
||
947 | ); |
||
948 | |||
949 | if ($deleteQuery !== false) { |
||
950 | $deleted = $deleteQuery->rowCount(); |
||
951 | $deletedCount += $deleted; |
||
952 | } |
||
953 | |||
954 | $thirdQuery = time(); |
||
955 | |||
956 | if ($this->echoCLI) { |
||
957 | echo ColorCLI::primary( |
||
958 | 'Finished deleting '.$deleted.' binaries with no collections or parts in '. |
||
959 | ($thirdQuery - $secondQuery).' seconds.' |
||
960 | ); |
||
961 | } |
||
962 | |||
963 | // orphaned parts - parts with no binary |
||
964 | // Don't delete currently inserting parts by checking the max id. |
||
965 | if ($this->echoCLI) { |
||
966 | echo ColorCLI::primary('Deleting orphaned parts with no binaries.'); |
||
967 | } |
||
968 | $deleted = 0; |
||
969 | $deleteQuery = $this->pdo->queryExec( |
||
970 | sprintf( |
||
971 | ' |
||
972 | DELETE p |
||
973 | FROM %s p |
||
974 | LEFT JOIN %s b ON p.binaries_id = b.id |
||
975 | WHERE b.id IS NULL |
||
976 | AND p.binaries_id < %d', |
||
977 | $this->tables['pname'], |
||
978 | $this->tables['bname'], |
||
979 | $this->maxQueryFormulator($this->tables['bname'], 20000) |
||
980 | ) |
||
981 | ); |
||
982 | if ($deleteQuery !== false) { |
||
983 | $deleted = $deleteQuery->rowCount(); |
||
984 | $deletedCount += $deleted; |
||
985 | } |
||
986 | |||
987 | $fourthQuery = time(); |
||
988 | |||
989 | if ($this->echoCLI) { |
||
990 | echo ColorCLI::primary( |
||
991 | 'Finished deleting '.$deleted.' parts with no binaries in '. |
||
992 | ($fourthQuery - $thirdQuery).' seconds.'.PHP_EOL |
||
993 | ); |
||
994 | } |
||
995 | } // done cleaning up Binaries/Parts orphans |
||
996 | |||
997 | if ($this->echoCLI) { |
||
998 | echo ColorCLI::primary( |
||
999 | 'Deleting collections that were missed after NZB creation.' |
||
1000 | ); |
||
1001 | } |
||
1002 | |||
1003 | $deleted = 0; |
||
1004 | // Collections that were missing on NZB creation. |
||
1005 | $collections = $this->pdo->queryDirect( |
||
1006 | sprintf( |
||
1007 | ' |
||
1008 | SELECT SQL_NO_CACHE c.id |
||
1009 | FROM %s c |
||
1010 | INNER JOIN releases r ON r.id = c.releases_id |
||
1011 | WHERE r.nzbstatus = 1', |
||
1012 | $this->tables['cname'] |
||
1013 | ) |
||
1014 | ); |
||
1015 | |||
1016 | if ($collections instanceof \Traversable) { |
||
1017 | foreach ($collections as $collection) { |
||
1018 | $deleted++; |
||
1019 | $this->pdo->queryExec( |
||
1020 | sprintf( |
||
1021 | ' |
||
1022 | DELETE c |
||
1023 | FROM %s c |
||
1024 | WHERE c.id = %d', |
||
1025 | $this->tables['cname'], |
||
1026 | $collection['id'] |
||
1027 | ) |
||
1028 | ); |
||
1029 | } |
||
1030 | $deletedCount += $deleted; |
||
1031 | } |
||
1032 | |||
1033 | if ($this->echoCLI) { |
||
1034 | ColorCLI::doEcho( |
||
1035 | ColorCLI::primary( |
||
1036 | 'Finished deleting '.$deleted.' collections missed after NZB creation in '. |
||
1037 | (time() - $fourthQuery).' seconds.'.PHP_EOL. |
||
1038 | 'Removed '. |
||
1039 | number_format($deletedCount). |
||
1040 | ' parts/binaries/collection rows in '. |
||
1041 | $this->consoleTools->convertTime($fourthQuery - $startTime).PHP_EOL |
||
1042 | ), true |
||
1043 | ); |
||
1044 | } |
||
1045 | } |
||
1046 | |||
1047 | /** |
||
1048 | * Delete unwanted releases based on admin settings. |
||
1049 | * This deletes releases based on group. |
||
1050 | * |
||
1051 | * @param int|string $groupID (optional) |
||
1052 | * |
||
1053 | * @void |
||
1054 | * @throws \Exception |
||
1055 | */ |
||
1056 | public function deletedReleasesByGroup($groupID = ''): void |
||
1057 | { |
||
1058 | $startTime = time(); |
||
1059 | $minSizeDeleted = $maxSizeDeleted = $minFilesDeleted = 0; |
||
1060 | |||
1061 | if ($this->echoCLI) { |
||
1062 | echo ColorCLI::header('Process Releases -> Delete releases smaller/larger than minimum size/file count from group/site setting.'); |
||
1063 | } |
||
1064 | |||
1065 | $groupID === '' ? $groupIDs = Group::getActiveIDs() : $groupIDs = [['id' => $groupID]]; |
||
1066 | |||
1067 | $maxSizeSetting = Settings::settingValue('.release.maxsizetoformrelease'); |
||
1068 | $minSizeSetting = Settings::settingValue('.release.minsizetoformrelease'); |
||
1069 | $minFilesSetting = Settings::settingValue('.release.minfilestoformrelease'); |
||
1070 | |||
1071 | foreach ($groupIDs as $grpID) { |
||
1072 | $releases = $this->pdo->queryDirect( |
||
1073 | sprintf( |
||
1074 | ' |
||
1075 | SELECT SQL_NO_CACHE r.guid, r.id |
||
1076 | FROM releases r |
||
1077 | INNER JOIN groups g ON g.id = r.groups_id |
||
1078 | WHERE r.groups_id = %d |
||
1079 | AND greatest(IFNULL(g.minsizetoformrelease, 0), %d) > 0 |
||
1080 | AND r.size < greatest(IFNULL(g.minsizetoformrelease, 0), %d)', |
||
1081 | $grpID['id'], |
||
1082 | $minSizeSetting, |
||
1083 | $minSizeSetting |
||
1084 | ) |
||
1085 | ); |
||
1086 | if ($releases instanceof \Traversable) { |
||
1087 | foreach ($releases as $release) { |
||
1088 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1089 | $minSizeDeleted++; |
||
1090 | } |
||
1091 | } |
||
1092 | |||
1093 | if ($maxSizeSetting > 0) { |
||
1094 | $releases = $this->pdo->queryDirect( |
||
1095 | sprintf( |
||
1096 | ' |
||
1097 | SELECT SQL_NO_CACHE id, guid |
||
1098 | FROM releases |
||
1099 | WHERE groups_id = %d |
||
1100 | AND size > %d', |
||
1101 | $grpID['id'], |
||
1102 | $maxSizeSetting |
||
1103 | ) |
||
1104 | ); |
||
1105 | if ($releases instanceof \Traversable) { |
||
1106 | foreach ($releases as $release) { |
||
1107 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1108 | $maxSizeDeleted++; |
||
1109 | } |
||
1110 | } |
||
1111 | } |
||
1112 | if ($minFilesSetting > 0) { |
||
1113 | $releases = $this->pdo->queryDirect( |
||
1114 | sprintf( |
||
1115 | ' |
||
1116 | SELECT SQL_NO_CACHE r.id, r.guid |
||
1117 | FROM releases r |
||
1118 | INNER JOIN groups g ON g.id = r.groups_id |
||
1119 | WHERE r.groups_id = %d |
||
1120 | AND greatest(IFNULL(g.minfilestoformrelease, 0), %d) > 0 |
||
1121 | AND r.totalpart < greatest(IFNULL(g.minfilestoformrelease, 0), %d)', |
||
1122 | $grpID['id'], |
||
1123 | $minFilesSetting, |
||
1124 | $minFilesSetting |
||
1125 | ) |
||
1126 | ); |
||
1127 | if ($releases instanceof \Traversable) { |
||
1128 | foreach ($releases as $release) { |
||
1129 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1130 | $minFilesDeleted++; |
||
1131 | } |
||
1132 | } |
||
1133 | } |
||
1134 | } |
||
1135 | |||
1136 | if ($this->echoCLI) { |
||
1137 | ColorCLI::doEcho( |
||
1138 | ColorCLI::primary( |
||
1139 | 'Deleted '.($minSizeDeleted + $maxSizeDeleted + $minFilesDeleted). |
||
1140 | ' releases: '.PHP_EOL. |
||
1141 | $minSizeDeleted.' smaller than, '.$maxSizeDeleted.' bigger than, '.$minFilesDeleted. |
||
1142 | ' with less files than site/groups setting in: '. |
||
1143 | $this->consoleTools->convertTime(time() - $startTime) |
||
1144 | ), |
||
1145 | true |
||
1146 | ); |
||
1147 | } |
||
1148 | } |
||
1149 | |||
1150 | /** |
||
1151 | * Delete releases using admin settings. |
||
1152 | * This deletes releases, regardless of group. |
||
1153 | * |
||
1154 | * @void |
||
1155 | * @throws \Exception |
||
1156 | */ |
||
1157 | public function deleteReleases(): void |
||
1158 | { |
||
1159 | $startTime = time(); |
||
1160 | $genres = new Genres(['Settings' => $this->pdo]); |
||
1161 | $passwordDeleted = $duplicateDeleted = $retentionDeleted = $completionDeleted = $disabledCategoryDeleted = 0; |
||
1162 | $disabledGenreDeleted = $miscRetentionDeleted = $miscHashedDeleted = $categoryMinSizeDeleted = 0; |
||
1163 | |||
1164 | // Delete old releases and finished collections. |
||
1165 | if ($this->echoCLI) { |
||
1166 | ColorCLI::doEcho(ColorCLI::header('Process Releases -> Delete old releases and passworded releases.'), true); |
||
1167 | } |
||
1168 | |||
1169 | // Releases past retention. |
||
1170 | if ((int) Settings::settingValue('..releaseretentiondays') !== 0) { |
||
1171 | $releases = $this->pdo->queryDirect( |
||
1172 | sprintf( |
||
1173 | 'SELECT SQL_NO_CACHE id, guid FROM releases WHERE postdate < (NOW() - INTERVAL %d DAY)', |
||
1174 | (int) Settings::settingValue('..releaseretentiondays') |
||
1175 | ) |
||
1176 | ); |
||
1177 | if ($releases instanceof \Traversable) { |
||
1178 | foreach ($releases as $release) { |
||
1179 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1180 | $retentionDeleted++; |
||
1181 | } |
||
1182 | } |
||
1183 | } |
||
1184 | |||
1185 | // Passworded releases. |
||
1186 | if ((int) Settings::settingValue('..deletepasswordedrelease') === 1) { |
||
1187 | $releases = $this->pdo->queryDirect( |
||
1188 | sprintf( |
||
1189 | 'SELECT SQL_NO_CACHE id, guid FROM releases WHERE passwordstatus = %d', |
||
1190 | Releases::PASSWD_RAR |
||
1191 | ) |
||
1192 | ); |
||
1193 | if ($releases instanceof \Traversable) { |
||
1194 | foreach ($releases as $release) { |
||
1195 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1196 | $passwordDeleted++; |
||
1197 | } |
||
1198 | } |
||
1199 | } |
||
1200 | |||
1201 | // Possibly passworded releases. |
||
1202 | if ((int) Settings::settingValue('..deletepossiblerelease') === 1) { |
||
1203 | $releases = $this->pdo->queryDirect( |
||
1204 | sprintf( |
||
1205 | 'SELECT SQL_NO_CACHE id, guid FROM releases WHERE passwordstatus = %d', |
||
1206 | Releases::PASSWD_POTENTIAL |
||
1207 | ) |
||
1208 | ); |
||
1209 | if ($releases instanceof \Traversable) { |
||
1210 | foreach ($releases as $release) { |
||
1211 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1212 | $passwordDeleted++; |
||
1213 | } |
||
1214 | } |
||
1215 | } |
||
1216 | |||
1217 | if ((int) $this->crossPostTime !== 0) { |
||
1218 | // Crossposted releases. |
||
1219 | $releases = $this->pdo->queryDirect( |
||
1220 | sprintf( |
||
1221 | 'SELECT SQL_NO_CACHE id, guid FROM releases WHERE adddate > (NOW() - INTERVAL %d HOUR) GROUP BY name HAVING COUNT(name) > 1', |
||
1222 | $this->crossPostTime |
||
1223 | ) |
||
1224 | ); |
||
1225 | if ($releases instanceof \Traversable) { |
||
1226 | foreach ($releases as $release) { |
||
1227 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1228 | $duplicateDeleted++; |
||
1229 | } |
||
1230 | } |
||
1231 | } |
||
1232 | |||
1233 | if ($this->completion > 0) { |
||
1234 | $releases = $this->pdo->queryDirect( |
||
1235 | sprintf('SELECT SQL_NO_CACHE id, guid FROM releases WHERE completion < %d AND completion > 0', $this->completion) |
||
1236 | ); |
||
1237 | if ($releases instanceof \Traversable) { |
||
1238 | foreach ($releases as $release) { |
||
1239 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1240 | $completionDeleted++; |
||
1241 | } |
||
1242 | } |
||
1243 | } |
||
1244 | |||
1245 | // Disabled categories. |
||
1246 | $disabledCategories = Category::getDisabledIDs(); |
||
1247 | if (\count($disabledCategories) > 0) { |
||
1248 | foreach ($disabledCategories as $disabledCategory) { |
||
1249 | $releases = $this->pdo->queryDirect( |
||
1250 | sprintf('SELECT SQL_NO_CACHE id, guid FROM releases WHERE categories_id = %d', (int) $disabledCategory['id']) |
||
1251 | ); |
||
1252 | if ($releases instanceof \Traversable) { |
||
1253 | foreach ($releases as $release) { |
||
1254 | $disabledCategoryDeleted++; |
||
1255 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1256 | } |
||
1257 | } |
||
1258 | } |
||
1259 | } |
||
1260 | |||
1261 | // Delete smaller than category minimum sizes. |
||
1262 | $categories = $this->pdo->queryDirect( |
||
1263 | ' |
||
1264 | SELECT SQL_NO_CACHE c.id AS id, |
||
1265 | CASE WHEN c.minsizetoformrelease = 0 THEN cp.minsizetoformrelease ELSE c.minsizetoformrelease END AS minsize |
||
1266 | FROM categories c |
||
1267 | INNER JOIN categories cp ON cp.id = c.parentid |
||
1268 | WHERE c.parentid IS NOT NULL' |
||
1269 | ); |
||
1270 | |||
1271 | if ($categories instanceof \Traversable) { |
||
1272 | foreach ($categories as $category) { |
||
1273 | if ((int) $category['minsize'] > 0) { |
||
1274 | $releases = $this->pdo->queryDirect( |
||
1275 | sprintf( |
||
1276 | ' |
||
1277 | SELECT SQL_NO_CACHE r.id, r.guid |
||
1278 | FROM releases r |
||
1279 | WHERE r.categories_id = %d |
||
1280 | AND r.size < %d |
||
1281 | LIMIT 1000', |
||
1282 | (int) $category['id'], |
||
1283 | (int) $category['minsize'] |
||
1284 | ) |
||
1285 | ); |
||
1286 | if ($releases instanceof \Traversable) { |
||
1287 | foreach ($releases as $release) { |
||
1288 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1289 | $categoryMinSizeDeleted++; |
||
1290 | } |
||
1291 | } |
||
1292 | } |
||
1293 | } |
||
1294 | } |
||
1295 | |||
1296 | // Disabled music genres. |
||
1297 | $genrelist = $genres->getDisabledIDs(); |
||
1298 | if (\count($genrelist) > 0) { |
||
1299 | foreach ($genrelist as $genre) { |
||
1300 | $releases = $this->pdo->queryDirect( |
||
1301 | sprintf( |
||
1302 | ' |
||
1303 | SELECT SQL_NO_CACHE id, guid |
||
1304 | FROM releases |
||
1305 | INNER JOIN |
||
1306 | ( |
||
1307 | SELECT id AS mid |
||
1308 | FROM musicinfo |
||
1309 | WHERE musicinfo.genre_id = %d |
||
1310 | ) mi ON musicinfo_id = mid', |
||
1311 | (int) $genre['id'] |
||
1312 | ) |
||
1313 | ); |
||
1314 | if ($releases instanceof \Traversable) { |
||
1315 | foreach ($releases as $release) { |
||
1316 | $disabledGenreDeleted++; |
||
1317 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1318 | } |
||
1319 | } |
||
1320 | } |
||
1321 | } |
||
1322 | |||
1323 | // Misc other. |
||
1324 | if (Settings::settingValue('..miscotherretentionhours') > 0) { |
||
1325 | $releases = $this->pdo->queryDirect( |
||
1326 | sprintf( |
||
1327 | ' |
||
1328 | SELECT SQL_NO_CACHE id, guid |
||
1329 | FROM releases |
||
1330 | WHERE categories_id = %d |
||
1331 | AND adddate <= NOW() - INTERVAL %d HOUR', |
||
1332 | Category::OTHER_MISC, |
||
1333 | (int) Settings::settingValue('..miscotherretentionhours') |
||
1334 | ) |
||
1335 | ); |
||
1336 | if ($releases instanceof \Traversable) { |
||
1337 | foreach ($releases as $release) { |
||
1338 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1339 | $miscRetentionDeleted++; |
||
1340 | } |
||
1341 | } |
||
1342 | } |
||
1343 | |||
1344 | // Misc hashed. |
||
1345 | if ((int) Settings::settingValue('..mischashedretentionhours') > 0) { |
||
1346 | $releases = $this->pdo->queryDirect( |
||
1347 | sprintf( |
||
1348 | ' |
||
1349 | SELECT SQL_NO_CACHE id, guid |
||
1350 | FROM releases |
||
1351 | WHERE categories_id = %d |
||
1352 | AND adddate <= NOW() - INTERVAL %d HOUR', |
||
1353 | Category::OTHER_HASHED, |
||
1354 | (int) Settings::settingValue('..mischashedretentionhours') |
||
1355 | ) |
||
1356 | ); |
||
1357 | if ($releases instanceof \Traversable) { |
||
1358 | foreach ($releases as $release) { |
||
1359 | $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage); |
||
1360 | $miscHashedDeleted++; |
||
1361 | } |
||
1362 | } |
||
1363 | } |
||
1364 | |||
1365 | if ($this->echoCLI) { |
||
1366 | ColorCLI::doEcho( |
||
1367 | ColorCLI::primary( |
||
1368 | 'Removed releases: '. |
||
1369 | number_format($retentionDeleted). |
||
1370 | ' past retention, '. |
||
1371 | number_format($passwordDeleted). |
||
1372 | ' passworded, '. |
||
1373 | number_format($duplicateDeleted). |
||
1374 | ' crossposted, '. |
||
1375 | number_format($disabledCategoryDeleted). |
||
1376 | ' from disabled categories, '. |
||
1377 | number_format($categoryMinSizeDeleted). |
||
1378 | ' smaller than category settings, '. |
||
1379 | number_format($disabledGenreDeleted). |
||
1380 | ' from disabled music genres, '. |
||
1381 | number_format($miscRetentionDeleted). |
||
1382 | ' from misc->other'. |
||
1383 | number_format($miscHashedDeleted). |
||
1384 | ' from misc->hashed'. |
||
1385 | ( |
||
1386 | $this->completion > 0 |
||
1387 | ? ', '.number_format($completionDeleted).' under '.$this->completion.'% completion.' |
||
1388 | : '.' |
||
1389 | ) |
||
1390 | ), true |
||
1391 | ); |
||
1392 | |||
1393 | $totalDeleted = ( |
||
1394 | $retentionDeleted + $passwordDeleted + $duplicateDeleted + $disabledCategoryDeleted + |
||
1395 | $disabledGenreDeleted + $miscRetentionDeleted + $miscHashedDeleted + $completionDeleted + |
||
1396 | $categoryMinSizeDeleted |
||
1397 | ); |
||
1398 | if ($totalDeleted > 0) { |
||
1399 | ColorCLI::doEcho( |
||
1400 | ColorCLI::primary( |
||
1401 | 'Removed '.number_format($totalDeleted).' releases in '. |
||
1402 | $this->consoleTools->convertTime(time() - $startTime) |
||
1403 | ), true |
||
1404 | ); |
||
1405 | } |
||
1406 | } |
||
1407 | } |
||
1408 | |||
1409 | /** |
||
1410 | * Formulate part of a query to prevent deletion of currently inserting parts / binaries / collections. |
||
1411 | * |
||
1412 | * @param string $groupName |
||
1413 | * @param int $difference |
||
1414 | * |
||
1415 | * @return string |
||
1416 | */ |
||
1417 | private function maxQueryFormulator($groupName, $difference): string |
||
1418 | { |
||
1419 | $maxID = $this->pdo->queryOneRow( |
||
1420 | sprintf( |
||
1421 | ' |
||
1422 | SELECT IFNULL(MAX(id),0) AS max |
||
1423 | FROM %s', |
||
1424 | $groupName |
||
1425 | ) |
||
1426 | ); |
||
1427 | |||
1428 | return empty($maxID['max']) || $maxID['max'] < $difference ? 0 : $maxID['max'] - $difference; |
||
1429 | } |
||
1430 | |||
1431 | /** |
||
1432 | * Look if we have all the files in a collection (which have the file count in the subject). |
||
1433 | * Set file check to complete. |
||
1434 | * This means the the binary table has the same count as the file count in the subject, but |
||
1435 | * the collection might not be complete yet since we might not have all the articles in the parts table. |
||
1436 | * |
||
1437 | * @param string $where |
||
1438 | * |
||
1439 | * @void |
||
1440 | */ |
||
1441 | private function collectionFileCheckStage1(&$where): void |
||
1464 | ) |
||
1465 | ); |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * The first query sets filecheck to COLLFC_ZEROPART if there's a file that starts with 0 (ex. [00/100]). |
||
1470 | * The second query sets filecheck to COLLFC_TEMPCOMP on everything left over, so anything that starts with 1 (ex. [01/100]). |
||
1471 | * |
||
1472 | * This is done because some collections start at 0 and some at 1, so if you were to assume the collection is complete |
||
1473 | * at 0 then you would never get a complete collection if it starts with 1 and if it starts, you can end up creating |
||
1474 | * a incomplete collection, since you assumed it was complete. |
||
1475 | * |
||
1476 | * @param string $where |
||
1477 | * |
||
1478 | * @void |
||
1479 | */ |
||
1480 | private function collectionFileCheckStage2(&$where): void |
||
1481 | { |
||
1482 | $this->pdo->queryExec( |
||
1483 | sprintf( |
||
1484 | ' |
||
1485 | UPDATE %s c |
||
1486 | INNER JOIN |
||
1487 | ( |
||
1488 | SELECT c.id |
||
1489 | FROM %s c |
||
1490 | INNER JOIN %s b ON b.collections_id = c.id |
||
1491 | WHERE b.filenumber = 0 |
||
1492 | AND c.totalfiles > 0 |
||
1493 | AND c.filecheck = %d %s |
||
1494 | GROUP BY c.id |
||
1495 | ) r ON c.id = r.id |
||
1496 | SET c.filecheck = %d', |
||
1497 | $this->tables['cname'], |
||
1498 | $this->tables['cname'], |
||
1499 | $this->tables['bname'], |
||
1500 | self::COLLFC_COMPCOLL, |
||
1501 | $where, |
||
1502 | self::COLLFC_ZEROPART |
||
1503 | ) |
||
1504 | ); |
||
1505 | $this->pdo->queryExec( |
||
1506 | sprintf( |
||
1507 | ' |
||
1508 | UPDATE %s c |
||
1509 | SET filecheck = %d |
||
1510 | WHERE filecheck = %d %s', |
||
1511 | $this->tables['cname'], |
||
1512 | self::COLLFC_TEMPCOMP, |
||
1513 | self::COLLFC_COMPCOLL, |
||
1514 | $where |
||
1515 | ) |
||
1516 | ); |
||
1517 | } |
||
1518 | |||
1519 | /** |
||
1520 | * Check if the files (binaries table) in a complete collection has all the parts. |
||
1521 | * If we have all the parts, set binaries table partcheck to FILE_COMPLETE. |
||
1522 | * |
||
1523 | * @param string $where |
||
1524 | * |
||
1525 | * @void |
||
1526 | */ |
||
1527 | private function collectionFileCheckStage3($where): void |
||
1528 | { |
||
1529 | $this->pdo->queryExec( |
||
1530 | sprintf( |
||
1531 | ' |
||
1532 | UPDATE %s b |
||
1533 | INNER JOIN |
||
1534 | ( |
||
1535 | SELECT b.id |
||
1536 | FROM %s b |
||
1537 | INNER JOIN %s c ON c.id = b.collections_id |
||
1538 | WHERE c.filecheck = %d |
||
1539 | AND b.partcheck = %d %s |
||
1540 | AND b.currentparts = b.totalparts |
||
1541 | GROUP BY b.id, b.totalparts |
||
1542 | ) r ON b.id = r.id |
||
1543 | SET b.partcheck = %d', |
||
1544 | $this->tables['bname'], |
||
1545 | $this->tables['bname'], |
||
1546 | $this->tables['cname'], |
||
1547 | self::COLLFC_TEMPCOMP, |
||
1548 | self::FILE_INCOMPLETE, |
||
1549 | $where, |
||
1550 | self::FILE_COMPLETE |
||
1551 | ) |
||
1552 | ); |
||
1553 | $this->pdo->queryExec( |
||
1554 | sprintf( |
||
1555 | ' |
||
1556 | UPDATE %s b |
||
1557 | INNER JOIN |
||
1558 | ( |
||
1559 | SELECT b.id |
||
1560 | FROM %s b |
||
1561 | INNER JOIN %s c ON c.id = b.collections_id |
||
1562 | WHERE c.filecheck = %d |
||
1563 | AND b.partcheck = %d %s |
||
1564 | AND b.currentparts >= (b.totalparts + 1) |
||
1565 | GROUP BY b.id, b.totalparts |
||
1566 | ) r ON b.id = r.id |
||
1567 | SET b.partcheck = %d', |
||
1568 | $this->tables['bname'], |
||
1569 | $this->tables['bname'], |
||
1570 | $this->tables['cname'], |
||
1571 | self::COLLFC_ZEROPART, |
||
1572 | self::FILE_INCOMPLETE, |
||
1573 | $where, |
||
1574 | self::FILE_COMPLETE |
||
1575 | ) |
||
1576 | ); |
||
1577 | } |
||
1578 | |||
1579 | /** |
||
1580 | * Check if all files (binaries table) for a collection are complete (if they all have the "parts"). |
||
1581 | * Set collections filecheck column to COLLFC_COMPPART. |
||
1582 | * This means the collection is complete. |
||
1583 | * |
||
1584 | * @param string $where |
||
1585 | * |
||
1586 | * @void |
||
1587 | */ |
||
1588 | private function collectionFileCheckStage4(&$where): void |
||
1589 | { |
||
1590 | $this->pdo->queryExec( |
||
1591 | sprintf( |
||
1592 | ' |
||
1593 | UPDATE %s c INNER JOIN |
||
1594 | (SELECT c.id FROM %s c |
||
1595 | INNER JOIN %s b ON c.id = b.collections_id |
||
1596 | WHERE b.partcheck = 1 AND c.filecheck IN (%d, %d) %s |
||
1597 | GROUP BY b.collections_id, c.totalfiles, c.id HAVING COUNT(b.id) >= c.totalfiles) |
||
1598 | r ON c.id = r.id SET filecheck = %d', |
||
1599 | $this->tables['cname'], |
||
1600 | $this->tables['cname'], |
||
1601 | $this->tables['bname'], |
||
1602 | self::COLLFC_TEMPCOMP, |
||
1603 | self::COLLFC_ZEROPART, |
||
1604 | $where, |
||
1605 | self::COLLFC_COMPPART |
||
1606 | ) |
||
1607 | ); |
||
1608 | } |
||
1609 | |||
1610 | /** |
||
1611 | * If not all files (binaries table) had their parts on the previous stage, |
||
1612 | * reset the collection filecheck column to COLLFC_COMPCOLL so we reprocess them next time. |
||
1613 | * |
||
1614 | * @param string $where |
||
1615 | * |
||
1616 | * @void |
||
1617 | */ |
||
1618 | private function collectionFileCheckStage5(&$where): void |
||
1619 | { |
||
1620 | $this->pdo->queryExec( |
||
1621 | sprintf( |
||
1622 | ' |
||
1623 | UPDATE %s c |
||
1624 | SET filecheck = %d |
||
1625 | WHERE filecheck IN (%d, %d) %s', |
||
1626 | $this->tables['cname'], |
||
1627 | self::COLLFC_COMPCOLL, |
||
1628 | self::COLLFC_TEMPCOMP, |
||
1629 | self::COLLFC_ZEROPART, |
||
1630 | $where |
||
1631 | ) |
||
1632 | ); |
||
1633 | } |
||
1634 | |||
1635 | /** |
||
1636 | * If a collection did not have the file count (ie: [00/12]) or the collection is incomplete after |
||
1637 | * $this->collectionDelayTime hours, set the collection to complete to create it into a release/nzb. |
||
1638 | * |
||
1639 | * @param string $where |
||
1640 | * |
||
1641 | * @void |
||
1642 | */ |
||
1643 | private function collectionFileCheckStage6(&$where): void |
||
1658 | ) |
||
1659 | ); |
||
1660 | } |
||
1661 | |||
1662 | /** |
||
1663 | * If a collection has been stuck for $this->collectionTimeout hours, delete it, it's bad. |
||
1664 | * |
||
1665 | * @param string $where |
||
1666 | * |
||
1667 | * @void |
||
1668 | * @throws \Exception |
||
1669 | */ |
||
1670 | private function processStuckCollections($where): void |
||
1671 | { |
||
1672 | $lastRun = Settings::settingValue('indexer.processing.last_run_time'); |
||
1673 | |||
1690 | ); |
||
1691 | } |
||
1694 |