Total Complexity | 146 |
Total Lines | 1168 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 0 | Features | 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 |
||
27 | class ProcessReleases |
||
28 | { |
||
29 | public const COLLFC_DEFAULT = 0; // Collection has default filecheck status |
||
30 | |||
31 | public const COLLFC_COMPCOLL = 1; // Collection is a complete collection |
||
32 | |||
33 | public const COLLFC_COMPPART = 2; // Collection is a complete collection and has all parts available |
||
34 | |||
35 | public const COLLFC_SIZED = 3; // Collection has been calculated for total size |
||
36 | |||
37 | public const COLLFC_INSERTED = 4; // Collection has been inserted into releases |
||
38 | |||
39 | public const COLLFC_DELETE = 5; // Collection is ready for deletion |
||
40 | |||
41 | public const COLLFC_TEMPCOMP = 15; // Collection is complete and being checked for complete parts |
||
42 | |||
43 | public const COLLFC_ZEROPART = 16; // Collection has a 00/0XX designator (temporary) |
||
44 | |||
45 | public const FILE_INCOMPLETE = 0; // We don't have all the parts yet for the file (binaries table partcheck column). |
||
46 | |||
47 | public const FILE_COMPLETE = 1; // We have all the parts for the file (binaries table partcheck column). |
||
48 | |||
49 | public int $collectionDelayTime; |
||
50 | |||
51 | public int $crossPostTime; |
||
52 | |||
53 | public int $releaseCreationLimit; |
||
54 | |||
55 | public int $completion; |
||
56 | |||
57 | public bool $echoCLI; |
||
58 | |||
59 | public \PDO $pdo; |
||
60 | |||
61 | public ColorCLI $colorCLI; |
||
62 | |||
63 | public NZB $nzb; |
||
64 | |||
65 | public ReleaseCleaning $releaseCleaning; |
||
66 | |||
67 | public Releases $releases; |
||
68 | |||
69 | public ReleaseImage $releaseImage; |
||
70 | |||
71 | /** |
||
72 | * Time (hours) to wait before delete a stuck/broken collection. |
||
73 | */ |
||
74 | private int $collectionTimeout; |
||
75 | |||
76 | public function __construct() |
||
77 | { |
||
78 | $this->echoCLI = config('nntmux.echocli'); |
||
79 | |||
80 | $this->colorCLI = new ColorCLI(); |
||
81 | $this->nzb = new NZB(); |
||
82 | $this->releaseCleaning = new ReleaseCleaning(); |
||
83 | $this->releases = new Releases(); |
||
84 | $this->releaseImage = new ReleaseImage(); |
||
85 | |||
86 | $dummy = Settings::settingValue('..delaytime'); |
||
87 | $this->collectionDelayTime = ($dummy !== '' ? (int) $dummy : 2); |
||
|
|||
88 | $dummy = Settings::settingValue('..crossposttime'); |
||
89 | $this->crossPostTime = ($dummy !== '' ? (int) $dummy : 2); |
||
90 | $dummy = Settings::settingValue('..maxnzbsprocessed'); |
||
91 | $this->releaseCreationLimit = ($dummy !== '' ? (int) $dummy : 1000); |
||
92 | $dummy = Settings::settingValue('..completionpercent'); |
||
93 | $this->completion = ($dummy !== '' ? (int) $dummy : 0); |
||
94 | if ($this->completion > 100) { |
||
95 | $this->completion = 100; |
||
96 | $this->colorCLI->error(PHP_EOL.'You have an invalid setting for completion. It cannot be higher than 100.'); |
||
97 | } |
||
98 | $this->collectionTimeout = (int) Settings::settingValue('indexer.processing.collection_timeout'); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Main method for creating releases/NZB files from collections. |
||
103 | * |
||
104 | * @param string $groupName (optional) |
||
105 | * |
||
106 | * @throws \Throwable |
||
107 | */ |
||
108 | public function processReleases(int $categorize, int $postProcess, string $groupName, NNTP $nntp): int |
||
109 | { |
||
110 | $this->echoCLI = config('nntmux.echocli'); |
||
111 | $groupID = ''; |
||
112 | |||
113 | if (! empty($groupName)) { |
||
114 | $groupInfo = UsenetGroup::getByName($groupName); |
||
115 | if ($groupInfo !== null) { |
||
116 | $groupID = $groupInfo['id']; |
||
117 | } |
||
118 | } |
||
119 | |||
120 | if ($this->echoCLI) { |
||
121 | $this->colorCLI->header('Starting release update process ('.now()->format('Y-m-d H:i:s').')'); |
||
122 | } |
||
123 | |||
124 | if (! file_exists(Settings::settingValue('..nzbpath'))) { |
||
125 | if ($this->echoCLI) { |
||
126 | $this->colorCLI->error('Bad or missing nzb directory - '.Settings::settingValue('..nzbpath')); |
||
127 | } |
||
128 | |||
129 | return 0; |
||
130 | } |
||
131 | |||
132 | $this->processIncompleteCollections($groupID); |
||
133 | $this->processCollectionSizes($groupID); |
||
134 | $this->deleteUnwantedCollections($groupID); |
||
135 | |||
136 | $totalReleasesAdded = 0; |
||
137 | do { |
||
138 | $releasesCount = $this->createReleases($groupID); |
||
139 | $totalReleasesAdded += $releasesCount['added']; |
||
140 | |||
141 | $nzbFilesAdded = $this->createNZBs($groupID); |
||
142 | |||
143 | $this->categorizeReleases($categorize, $groupID); |
||
144 | $this->postProcessReleases($postProcess, $nntp); |
||
145 | $this->deleteCollections($groupID); |
||
146 | |||
147 | // This loops as long as the number of releases or nzbs added was >= the limit (meaning there are more waiting to be created) |
||
148 | } while ( |
||
149 | ($releasesCount['added'] + $releasesCount['dupes']) >= $this->releaseCreationLimit |
||
150 | || $nzbFilesAdded >= $this->releaseCreationLimit |
||
151 | ); |
||
152 | |||
153 | $this->deleteReleases(); |
||
154 | |||
155 | return $totalReleasesAdded; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Return all releases to other->misc category. |
||
160 | * |
||
161 | * @param string $where Optional "where" query parameter. |
||
162 | * |
||
163 | * @void |
||
164 | */ |
||
165 | public function resetCategorize(string $where = ''): void |
||
166 | { |
||
167 | DB::update( |
||
168 | sprintf('UPDATE releases SET categories_id = %d, iscategorized = 0 %s', Category::OTHER_MISC, $where) |
||
169 | ); |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Categorizes releases. |
||
174 | * |
||
175 | * @param string $type name or searchname | Categorize using the search name or subject. |
||
176 | * @return int Quantity of categorized releases. |
||
177 | * |
||
178 | * @throws \Exception |
||
179 | */ |
||
180 | public function categorizeRelease(string $type, $groupId): int |
||
181 | { |
||
182 | $cat = new Categorize(); |
||
183 | $categorized = $total = 0; |
||
184 | $releasesQuery = Release::query()->where(['categories_id' => Category::OTHER_MISC, 'iscategorized' => 0]); |
||
185 | if (! empty($groupId)) { |
||
186 | $releasesQuery->where('groups_id', $groupId); |
||
187 | } |
||
188 | $releases = $releasesQuery->select(['id', 'fromname', 'groups_id', $type])->get(); |
||
189 | if ($releases->count() > 0) { |
||
190 | $total = \count($releases); |
||
191 | foreach ($releases as $release) { |
||
192 | $catId = $cat->determineCategory($release->groups_id, $release->{$type}, $release->fromname); |
||
193 | Release::query()->where('id', $release->id)->update(['categories_id' => $catId['categories_id'], 'iscategorized' => 1]); |
||
194 | $categorized++; |
||
195 | if ($this->echoCLI) { |
||
196 | $this->colorCLI->overWritePrimary( |
||
197 | 'Categorizing: '.$this->colorCLI->percentString($categorized, $total) |
||
198 | ); |
||
199 | } |
||
200 | } |
||
201 | } |
||
202 | if ($this->echoCLI && $categorized > 0) { |
||
203 | echo PHP_EOL; |
||
204 | } |
||
205 | |||
206 | return $categorized; |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * @throws \Exception |
||
211 | * @throws \Throwable |
||
212 | */ |
||
213 | public function processIncompleteCollections($groupID): void |
||
214 | { |
||
215 | $startTime = now()->toImmutable(); |
||
216 | |||
217 | if ($this->echoCLI) { |
||
218 | $this->colorCLI->header('Process Releases -> Attempting to find complete collections.'); |
||
219 | } |
||
220 | |||
221 | $where = (! empty($groupID) ? ' AND c.groups_id = '.$groupID.' ' : ' '); |
||
222 | |||
223 | $this->processStuckCollections($groupID); |
||
224 | $this->collectionFileCheckStage1($groupID); |
||
225 | $this->collectionFileCheckStage2($groupID); |
||
226 | $this->collectionFileCheckStage3($where); |
||
227 | $this->collectionFileCheckStage4($where); |
||
228 | $this->collectionFileCheckStage5($groupID); |
||
229 | $this->collectionFileCheckStage6($where); |
||
230 | |||
231 | if ($this->echoCLI) { |
||
232 | $countQuery = Collection::query()->where('filecheck', self::COLLFC_COMPPART); |
||
233 | |||
234 | if (! empty($groupID)) { |
||
235 | $countQuery->where('groups_id', $groupID); |
||
236 | } |
||
237 | $count = $countQuery->count('id'); |
||
238 | |||
239 | $totalTime = now()->diffInSeconds($startTime); |
||
240 | |||
241 | $this->colorCLI->primary( |
||
242 | ($count ?? 0).' collections were found to be complete. Time: '. |
||
243 | $totalTime.Str::plural(' second', $totalTime), |
||
244 | true |
||
245 | ); |
||
246 | } |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @throws \Exception |
||
251 | * @throws \Throwable |
||
252 | */ |
||
253 | public function processCollectionSizes($groupID): void |
||
254 | { |
||
255 | $startTime = now()->toImmutable(); |
||
256 | |||
257 | if ($this->echoCLI) { |
||
258 | $this->colorCLI->header('Process Releases -> Calculating collection sizes (in bytes).'); |
||
259 | } |
||
260 | // Get the total size in bytes of the collection for collections where filecheck = 2. |
||
261 | DB::transaction(function () use ($groupID, $startTime) { |
||
262 | $checked = DB::update( |
||
263 | sprintf( |
||
264 | ' |
||
265 | UPDATE collections c |
||
266 | SET c.filesize = |
||
267 | ( |
||
268 | SELECT COALESCE(SUM(b.partsize), 0) |
||
269 | FROM binaries b |
||
270 | WHERE b.collections_id = c.id |
||
271 | ), |
||
272 | c.filecheck = %d |
||
273 | WHERE c.filecheck = %d |
||
274 | AND c.filesize = 0 %s', |
||
275 | self::COLLFC_SIZED, |
||
276 | self::COLLFC_COMPPART, |
||
277 | (! empty($groupID) ? ' AND c.groups_id = '.$groupID : ' ') |
||
278 | ) |
||
279 | ); |
||
280 | if ($checked > 0 && $this->echoCLI) { |
||
281 | $this->colorCLI->primary( |
||
282 | $checked.' collections set to filecheck = 3(size calculated)', |
||
283 | true |
||
284 | ); |
||
285 | $totalTime = now()->diffInSeconds($startTime); |
||
286 | $this->colorCLI->primary($totalTime.Str::plural(' second', $totalTime), true); |
||
287 | } |
||
288 | }, 10); |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * @throws \Exception |
||
293 | * @throws \Throwable |
||
294 | */ |
||
295 | public function deleteUnwantedCollections($groupID): void |
||
296 | { |
||
297 | $startTime = now()->toImmutable(); |
||
298 | |||
299 | if ($this->echoCLI) { |
||
300 | $this->colorCLI->header('Process Releases -> Delete collections smaller/larger than minimum size/file count from group/site setting.'); |
||
301 | } |
||
302 | |||
303 | $groupID === '' ? $groupIDs = UsenetGroup::getActiveIDs() : $groupIDs = [['id' => $groupID]]; |
||
304 | |||
305 | $minSizeDeleted = $maxSizeDeleted = $minFilesDeleted = 0; |
||
306 | |||
307 | $maxSizeSetting = Settings::settingValue('.release.maxsizetoformrelease'); |
||
308 | $minSizeSetting = Settings::settingValue('.release.minsizetoformrelease'); |
||
309 | $minFilesSetting = Settings::settingValue('.release.minfilestoformrelease'); |
||
310 | |||
311 | foreach ($groupIDs as $grpID) { |
||
312 | $groupMinSizeSetting = $groupMinFilesSetting = 0; |
||
313 | |||
314 | $groupMinimums = UsenetGroup::getGroupByID($grpID['id']); |
||
315 | if ($groupMinimums !== null) { |
||
316 | if (! empty($groupMinimums['minsizetoformrelease']) && $groupMinimums['minsizetoformrelease'] > 0) { |
||
317 | $groupMinSizeSetting = (int) $groupMinimums['minsizetoformrelease']; |
||
318 | } |
||
319 | if (! empty($groupMinimums['minfilestoformrelease']) && $groupMinimums['minfilestoformrelease'] > 0) { |
||
320 | $groupMinFilesSetting = (int) $groupMinimums['minfilestoformrelease']; |
||
321 | } |
||
322 | } |
||
323 | |||
324 | if (Collection::query()->where('filecheck', self::COLLFC_SIZED)->where('filesize', '>', 0)->first() !== null) { |
||
325 | DB::transaction(function () use ( |
||
326 | $groupMinSizeSetting, |
||
327 | $minSizeSetting, |
||
328 | $minSizeDeleted, |
||
329 | $maxSizeSetting, |
||
330 | $maxSizeDeleted, |
||
331 | $minFilesSetting, |
||
332 | $groupMinFilesSetting, |
||
333 | $minFilesDeleted, |
||
334 | $startTime |
||
335 | ) { |
||
336 | $deleteQuery = Collection::query() |
||
337 | ->where('filecheck', self::COLLFC_SIZED) |
||
338 | ->where('filesize', '>', 0) |
||
339 | ->whereRaw('GREATEST(?, ?) > 0 AND filesize < GREATEST(?, ?)', [$groupMinSizeSetting, $minSizeSetting, $groupMinSizeSetting, $minSizeSetting]) |
||
340 | ->delete(); |
||
341 | |||
342 | if ($deleteQuery > 0) { |
||
343 | $minSizeDeleted += $deleteQuery; |
||
344 | } |
||
345 | |||
346 | if ($maxSizeSetting > 0) { |
||
347 | $deleteQuery = Collection::query() |
||
348 | ->where('filecheck', '=', self::COLLFC_SIZED) |
||
349 | ->where('filesize', '>', $maxSizeSetting) |
||
350 | ->delete(); |
||
351 | |||
352 | if ($deleteQuery > 0) { |
||
353 | $maxSizeDeleted += $deleteQuery; |
||
354 | } |
||
355 | } |
||
356 | |||
357 | if ($minFilesSetting > 0 || $groupMinFilesSetting > 0) { |
||
358 | $deleteQuery = Collection::query() |
||
359 | ->where('filecheck', self::COLLFC_SIZED) |
||
360 | ->where('filesize', '>', 0) |
||
361 | ->whereRaw('GREATEST(?, ?) > 0 AND totalfiles < GREATEST(?, ?)', [$groupMinFilesSetting, $minFilesSetting, $groupMinFilesSetting, $minFilesSetting]) |
||
362 | ->delete(); |
||
363 | |||
364 | if ($deleteQuery > 0) { |
||
365 | $minFilesDeleted += $deleteQuery; |
||
366 | } |
||
367 | } |
||
368 | |||
369 | $totalTime = now()->diffInSeconds($startTime); |
||
370 | |||
371 | if ($this->echoCLI) { |
||
372 | $this->colorCLI->primary('Deleted '.($minSizeDeleted + $maxSizeDeleted + $minFilesDeleted).' collections: '.PHP_EOL.$minSizeDeleted.' smaller than, '.$maxSizeDeleted.' bigger than, '.$minFilesDeleted.' with less files than site/group settings in: '.$totalTime.Str::plural(' second', $totalTime), true); |
||
373 | } |
||
374 | }, 10); |
||
375 | } |
||
376 | } |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * @param int|string $groupID (optional) |
||
381 | * |
||
382 | * @throws \Throwable |
||
383 | */ |
||
384 | #[ArrayShape(['added' => 'int', 'dupes' => 'int'])] |
||
385 | public function createReleases(int|string $groupID): array |
||
386 | { |
||
387 | $startTime = now()->toImmutable(); |
||
388 | |||
389 | $categorize = new Categorize(); |
||
390 | $returnCount = $duplicate = 0; |
||
391 | |||
392 | if ($this->echoCLI) { |
||
393 | $this->colorCLI->header('Process Releases -> Create releases from complete collections.'); |
||
394 | } |
||
395 | $collectionsQuery = Collection::query() |
||
396 | ->where('collections.filecheck', self::COLLFC_SIZED) |
||
397 | ->where('collections.filesize', '>', 0); |
||
398 | if (! empty($groupID)) { |
||
399 | $collectionsQuery->where('collections.groups_id', $groupID); |
||
400 | } |
||
401 | $collectionsQuery->select(['collections.*', 'usenet_groups.name as gname']) |
||
402 | ->join('usenet_groups', 'usenet_groups.id', '=', 'collections.groups_id') |
||
403 | ->limit($this->releaseCreationLimit); |
||
404 | $collections = $collectionsQuery->get(); |
||
405 | if ($this->echoCLI && $collections->count() > 0) { |
||
406 | $this->colorCLI->primary(\count($collections).' Collections ready to be converted to releases.', true); |
||
407 | } |
||
408 | |||
409 | foreach ($collections as $collection) { |
||
410 | $cleanRelName = mb_convert_encoding(str_replace(['#', '@', '$', '%', '^', '§', '¨', '©', 'Ö'], '', $collection->subject), 'UTF-8', mb_list_encodings()); |
||
411 | $fromName = mb_convert_encoding( |
||
412 | trim($collection->fromname, "'"), 'UTF-8', mb_list_encodings() |
||
413 | ); |
||
414 | |||
415 | // Look for duplicates, duplicates match on releases.name, releases.fromname and releases.size |
||
416 | // A 1% variance in size is considered the same size when the subject and poster are the same |
||
417 | $dupeCheck = Release::query() |
||
418 | ->where(['name' => $cleanRelName, 'fromname' => $fromName]) |
||
419 | ->whereBetween('size', [$collection->filesize * .99, $collection->filesize * 1.01]) |
||
420 | ->first(['id']); |
||
421 | |||
422 | if ($dupeCheck === null) { |
||
423 | $cleanedName = $this->releaseCleaning->releaseCleaner( |
||
424 | $collection->subject, |
||
425 | $collection->fromname, |
||
426 | $collection->gname |
||
427 | ); |
||
428 | |||
429 | if (\is_array($cleanedName)) { |
||
430 | $properName = $cleanedName['properlynamed'] ?? false; |
||
431 | $preID = $cleanedName['predb'] ?? false; |
||
432 | $cleanedName = $cleanedName['cleansubject'] ?? $cleanRelName; |
||
433 | } else { |
||
434 | $properName = true; |
||
435 | $preID = false; |
||
436 | } |
||
437 | |||
438 | if ($preID === false && $cleanedName !== '') { |
||
439 | // try to match the cleaned searchname to predb title or filename here |
||
440 | $preMatch = Predb::matchPre($cleanedName); |
||
441 | if ($preMatch !== false) { |
||
442 | $cleanedName = $preMatch['title']; |
||
443 | $preID = $preMatch['predb_id']; |
||
444 | $properName = true; |
||
445 | } |
||
446 | } |
||
447 | |||
448 | $determinedCategory = $categorize->determineCategory($collection->groups_id, $cleanedName); |
||
449 | |||
450 | $releaseID = Release::insertRelease( |
||
451 | [ |
||
452 | 'name' => $cleanRelName, |
||
453 | 'searchname' => ! empty($cleanedName) ? mb_convert_encoding($cleanedName, 'UTF-8', mb_list_encodings()) : $cleanRelName, |
||
454 | 'totalpart' => $collection->totalfiles, |
||
455 | 'groups_id' => $collection->groups_id, |
||
456 | 'guid' => createGUID(), |
||
457 | 'postdate' => $collection->date, |
||
458 | 'fromname' => $fromName, |
||
459 | 'size' => $collection->filesize, |
||
460 | 'categories_id' => $determinedCategory['categories_id'] ?? Category::OTHER_MISC, |
||
461 | 'isrenamed' => $properName === true ? 1 : 0, |
||
462 | 'predb_id' => $preID === false ? 0 : $preID, |
||
463 | 'nzbstatus' => NZB::NZB_NONE, |
||
464 | ] |
||
465 | ); |
||
466 | |||
467 | if ($releaseID !== null) { |
||
468 | // Update collections table to say we inserted the release. |
||
469 | DB::transaction(static function () use ($collection, $releaseID) { |
||
470 | Collection::query()->where('id', $collection->id)->update(['filecheck' => self::COLLFC_INSERTED, 'releases_id' => $releaseID]); |
||
471 | }, 10); |
||
472 | |||
473 | // Add the id of regex that matched the collection and release name to release_regexes table |
||
474 | ReleaseRegex::insertOrIgnore([ |
||
475 | 'releases_id' => $releaseID, |
||
476 | 'collection_regex_id' => $collection->collection_regexes_id, |
||
477 | 'naming_regex_id' => $cleanedName['id'] ?? 0, |
||
478 | ]); |
||
479 | |||
480 | if (preg_match_all('#(\S+):\S+#', $collection->xref, $hits)) { |
||
481 | foreach ($hits[1] as $grp) { |
||
482 | //check if the group name is in a valid format |
||
483 | $grpTmp = UsenetGroup::isValidGroup($grp); |
||
484 | if ($grpTmp !== false) { |
||
485 | //check if the group already exists in database |
||
486 | $xrefGrpID = UsenetGroup::getIDByName($grpTmp); |
||
487 | if ($xrefGrpID === '') { |
||
488 | $xrefGrpID = UsenetGroup::addGroup( |
||
489 | [ |
||
490 | 'name' => $grpTmp, |
||
491 | 'description' => 'Added by Release processing', |
||
492 | 'backfill_target' => 1, |
||
493 | 'first_record' => 0, |
||
494 | 'last_record' => 0, |
||
495 | 'active' => 0, |
||
496 | 'backfill' => 0, |
||
497 | 'minfilestoformrelease' => '', |
||
498 | 'minsizetoformrelease' => '', |
||
499 | ] |
||
500 | ); |
||
501 | } |
||
502 | |||
503 | $relGroupsChk = ReleasesGroups::query()->where( |
||
504 | [ |
||
505 | ['releases_id', '=', $releaseID], |
||
506 | ['groups_id', '=', $xrefGrpID], |
||
507 | ] |
||
508 | )->first(); |
||
509 | |||
510 | if ($relGroupsChk === null) { |
||
511 | ReleasesGroups::query()->insert( |
||
512 | [ |
||
513 | 'releases_id' => $releaseID, |
||
514 | 'groups_id' => $xrefGrpID, |
||
515 | ] |
||
516 | ); |
||
517 | } |
||
518 | } |
||
519 | } |
||
520 | } |
||
521 | |||
522 | $returnCount++; |
||
523 | |||
524 | if ($this->echoCLI) { |
||
525 | echo "Added $returnCount releases.\r"; |
||
526 | } |
||
527 | } |
||
528 | } else { |
||
529 | // The release was already in the DB, so delete the collection. |
||
530 | DB::transaction(static function () use ($collection) { |
||
531 | Collection::query()->where('collectionhash', $collection->collectionhash)->delete(); |
||
532 | }, 10); |
||
533 | |||
534 | $duplicate++; |
||
535 | } |
||
536 | } |
||
537 | |||
538 | $totalTime = now()->diffInSeconds($startTime); |
||
539 | |||
540 | if ($this->echoCLI) { |
||
541 | $this->colorCLI->primary( |
||
542 | PHP_EOL. |
||
543 | number_format($returnCount). |
||
544 | ' Releases added and '. |
||
545 | number_format($duplicate). |
||
546 | ' duplicate collections deleted in '. |
||
547 | $totalTime.Str::plural(' second', $totalTime), |
||
548 | true |
||
549 | ); |
||
550 | } |
||
551 | |||
552 | return ['added' => $returnCount, 'dupes' => $duplicate]; |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * Create NZB files from complete releases. |
||
557 | * |
||
558 | * @param int|string $groupID (optional) |
||
559 | * |
||
560 | * @throws \Throwable |
||
561 | */ |
||
562 | public function createNZBs(int|string $groupID): int |
||
563 | { |
||
564 | $startTime = now()->toImmutable(); |
||
565 | |||
566 | if ($this->echoCLI) { |
||
567 | $this->colorCLI->header('Process Releases -> Create the NZB, delete collections/binaries/parts.'); |
||
568 | } |
||
569 | |||
570 | $releasesQuery = Release::query()->with('category.parent')->where('nzbstatus', '=', 0); |
||
571 | if (! empty($groupID)) { |
||
572 | $releasesQuery->where('releases.groups_id', $groupID); |
||
573 | } |
||
574 | $releases = $releasesQuery->select(['id', 'guid', 'name', 'categories_id'])->get(); |
||
575 | |||
576 | $nzbCount = 0; |
||
577 | |||
578 | if ($releases->count() > 0) { |
||
579 | $total = $releases->count(); |
||
580 | foreach ($releases as $release) { |
||
581 | if ($this->nzb->writeNzbForReleaseId($release)) { |
||
582 | $nzbCount++; |
||
583 | if ($this->echoCLI) { |
||
584 | echo "Creating NZBs and deleting Collections: $nzbCount/$total.\r"; |
||
585 | } |
||
586 | } |
||
587 | } |
||
588 | } |
||
589 | |||
590 | $totalTime = now()->diffInSeconds($startTime); |
||
591 | |||
592 | if ($this->echoCLI) { |
||
593 | $this->colorCLI->primary( |
||
594 | number_format($nzbCount).' NZBs created/Collections deleted in '. |
||
595 | $totalTime.Str::plural(' second', $totalTime).PHP_EOL. |
||
596 | 'Total time: '.$totalTime.Str::plural(' second', $totalTime), |
||
597 | true |
||
598 | ); |
||
599 | } |
||
600 | |||
601 | return $nzbCount; |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * Categorize releases. |
||
606 | * |
||
607 | * @param int|string $groupID (optional) |
||
608 | * |
||
609 | * @void |
||
610 | * |
||
611 | * @throws \Exception |
||
612 | */ |
||
613 | public function categorizeReleases(int $categorize, int|string $groupID = ''): void |
||
614 | { |
||
615 | $startTime = now()->toImmutable(); |
||
616 | if ($this->echoCLI) { |
||
617 | $this->colorCLI->header('Process Releases -> Categorize releases.'); |
||
618 | } |
||
619 | $type = match ((int) $categorize) { |
||
620 | 2 => 'searchname', |
||
621 | default => 'name', |
||
622 | }; |
||
623 | $this->categorizeRelease( |
||
624 | $type, |
||
625 | $groupID |
||
626 | ); |
||
627 | |||
628 | $totalTime = now()->diffInSeconds($startTime); |
||
629 | |||
630 | if ($this->echoCLI) { |
||
631 | $this->colorCLI->primary($totalTime.Str::plural(' second', $totalTime)); |
||
632 | } |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * Post-process releases. |
||
637 | * |
||
638 | * |
||
639 | * @void |
||
640 | * |
||
641 | * @throws \Exception |
||
642 | */ |
||
643 | public function postProcessReleases(int $postProcess, NNTP $nntp): void |
||
644 | { |
||
645 | if ((int) $postProcess === 1) { |
||
646 | (new PostProcess(['Echo' => $this->echoCLI]))->processAll($nntp); |
||
647 | } elseif ($this->echoCLI) { |
||
648 | $this->colorCLI->info( |
||
649 | 'Post-processing is not running inside the Process Releases class.'.PHP_EOL. |
||
650 | 'If you are using tmux or screen they might have their own scripts running Post-processing.' |
||
651 | ); |
||
652 | } |
||
653 | } |
||
654 | |||
655 | /** |
||
656 | * @throws \Exception |
||
657 | * @throws \Throwable |
||
658 | */ |
||
659 | public function deleteCollections($groupID): void |
||
660 | { |
||
661 | $startTime = now()->toImmutable(); |
||
662 | |||
663 | $deletedCount = 0; |
||
664 | |||
665 | // CBP older than retention. |
||
666 | if ($this->echoCLI) { |
||
667 | echo $this->colorCLI->header('Process Releases -> Delete finished collections.'.PHP_EOL). |
||
668 | $this->colorCLI->primary(sprintf( |
||
669 | 'Deleting collections/binaries/parts older than %d hours.', |
||
670 | Settings::settingValue('..partretentionhours') |
||
671 | ), true); |
||
672 | } |
||
673 | |||
674 | DB::transaction(function () use ($deletedCount, $startTime) { |
||
675 | $deleted = 0; |
||
676 | $deleteQuery = Collection::query() |
||
677 | ->where('dateadded', '<', now()->subHours(Settings::settingValue('..partretentionhours'))) |
||
678 | ->delete(); |
||
679 | if ($deleteQuery > 0) { |
||
680 | $deleted = $deleteQuery; |
||
681 | $deletedCount += $deleted; |
||
682 | } |
||
683 | $firstQuery = $fourthQuery = now(); |
||
684 | |||
685 | $totalTime = $firstQuery->diffInSeconds($startTime); |
||
686 | |||
687 | if ($this->echoCLI) { |
||
688 | $this->colorCLI->primary( |
||
689 | 'Finished deleting '.$deleted.' old collections/binaries/parts in '. |
||
690 | $totalTime.Str::plural(' second', $totalTime), |
||
691 | true |
||
692 | ); |
||
693 | } |
||
694 | |||
695 | // Cleanup orphaned collections, binaries and parts |
||
696 | // this really shouldn't happen, but just incase - so we only run 1/200 of the time |
||
697 | if (random_int(0, 200) <= 1) { |
||
698 | // CBP collection orphaned with no binaries or parts. |
||
699 | if ($this->echoCLI) { |
||
700 | echo $this->colorCLI->header('Process Releases -> Remove CBP orphans.'.PHP_EOL).$this->colorCLI->primary('Deleting orphaned collections.'); |
||
701 | } |
||
702 | |||
703 | $deleted = 0; |
||
704 | $deleteQuery = Collection::query()->whereNull('binaries.id')->orWhereNull('parts.binaries_id')->leftJoin('binaries', 'collections.id', '=', 'binaries.collections_id')->leftJoin('parts', 'binaries.id', '=', 'parts.binaries_id')->delete(); |
||
705 | |||
706 | if ($deleteQuery > 0) { |
||
707 | $deleted = $deleteQuery; |
||
708 | $deletedCount += $deleted; |
||
709 | } |
||
710 | |||
711 | $totalTime = now()->diffInSeconds($firstQuery); |
||
712 | |||
713 | if ($this->echoCLI) { |
||
714 | $this->colorCLI->primary('Finished deleting '.$deleted.' orphaned collections in '.$totalTime.Str::plural(' second', $totalTime), true); |
||
715 | } |
||
716 | } |
||
717 | |||
718 | if ($this->echoCLI) { |
||
719 | $this->colorCLI->primary('Deleting collections that were missed after NZB creation.', true); |
||
720 | } |
||
721 | |||
722 | $deleted = 0; |
||
723 | // Collections that were missing on NZB creation. |
||
724 | $collections = Collection::query()->where('releases.nzbstatus', '=', 1)->leftJoin('releases', 'releases.id', '=', 'collections.releases_id')->select('collections.id')->get(); |
||
725 | |||
726 | foreach ($collections as $collection) { |
||
727 | $deleted++; |
||
728 | Collection::query()->where('id', $collection->id)->delete(); |
||
729 | } |
||
730 | $deletedCount += $deleted; |
||
731 | |||
732 | $colDelTime = now()->diffInSeconds($fourthQuery); |
||
733 | $totalTime = $fourthQuery->diffInSeconds($startTime); |
||
734 | |||
735 | if ($this->echoCLI) { |
||
736 | $this->colorCLI->primary('Finished deleting '.$deleted.' collections missed after NZB creation in '.$colDelTime.Str::plural(' second', $colDelTime).PHP_EOL.'Removed '.number_format($deletedCount).' parts/binaries/collection rows in '.$totalTime.Str::plural(' second', $totalTime), true); |
||
737 | } |
||
738 | }, 10); |
||
739 | } |
||
740 | |||
741 | /** |
||
742 | * Delete unwanted releases based on admin settings. |
||
743 | * This deletes releases based on group. |
||
744 | * |
||
745 | * @param int|string $groupID (optional) |
||
746 | * |
||
747 | * @void |
||
748 | * |
||
749 | * @throws \Exception |
||
750 | */ |
||
751 | public function deletedReleasesByGroup(int|string $groupID = ''): void |
||
752 | { |
||
753 | $startTime = now()->toImmutable(); |
||
754 | $minSizeDeleted = $maxSizeDeleted = $minFilesDeleted = 0; |
||
755 | |||
756 | if ($this->echoCLI) { |
||
757 | $this->colorCLI->header('Process Releases -> Delete releases smaller/larger than minimum size/file count from group/site setting.'); |
||
758 | } |
||
759 | |||
760 | $groupIDs = $groupID === '' ? UsenetGroup::getActiveIDs() : [['id' => $groupID]]; |
||
761 | |||
762 | $maxSizeSetting = Settings::settingValue('.release.maxsizetoformrelease'); |
||
763 | $minSizeSetting = Settings::settingValue('.release.minsizetoformrelease'); |
||
764 | $minFilesSetting = Settings::settingValue('.release.minfilestoformrelease'); |
||
765 | |||
766 | foreach ($groupIDs as $grpID) { |
||
767 | $releases = Release::query()->where('releases.groups_id', $grpID['id'])->whereRaw('greatest(IFNULL(usenet_groups.minsizetoformrelease, 0), ?) > 0 AND releases.size < greatest(IFNULL(usenet_groups.minsizetoformrelease, 0), ?)', [$minSizeSetting, $minSizeSetting])->join('usenet_groups', 'usenet_groups.id', '=', 'releases.groups_id')->select(['releases.id', 'releases.guid'])->get(); |
||
768 | foreach ($releases as $release) { |
||
769 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
770 | $minSizeDeleted++; |
||
771 | } |
||
772 | |||
773 | if ($maxSizeSetting > 0) { |
||
774 | $releases = Release::query()->where('groups_id', $grpID['id'])->where('size', '>', $maxSizeSetting)->select(['id', 'guid'])->get(); |
||
775 | foreach ($releases as $release) { |
||
776 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
777 | $maxSizeDeleted++; |
||
778 | } |
||
779 | } |
||
780 | if ($minFilesSetting > 0) { |
||
781 | $releases = Release::query()->where('releases.groups_id', $grpID['id'])->whereRaw('greatest(IFNULL(usenet_groups.minfilestoformrelease, 0), ?) > 0 AND releases.totalpart < greatest(IFNULL(usenet_groups.minfilestoformrelease, 0), ?)', [$minFilesSetting, $minFilesSetting])->join('usenet_groups', 'usenet_groups.id', '=', 'releases.groups_id')->get(); |
||
782 | foreach ($releases as $release) { |
||
783 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
784 | $minFilesDeleted++; |
||
785 | } |
||
786 | } |
||
787 | } |
||
788 | |||
789 | $totalTime = now()->diffInSeconds($startTime); |
||
790 | |||
791 | if ($this->echoCLI) { |
||
792 | $this->colorCLI->primary( |
||
793 | 'Deleted '.($minSizeDeleted + $maxSizeDeleted + $minFilesDeleted). |
||
794 | ' releases: '.PHP_EOL. |
||
795 | $minSizeDeleted.' smaller than, '.$maxSizeDeleted.' bigger than, '.$minFilesDeleted. |
||
796 | ' with less files than site/groups setting in: '. |
||
797 | $totalTime.Str::plural(' second', $totalTime), |
||
798 | true |
||
799 | ); |
||
800 | } |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * Delete releases using admin settings. |
||
805 | * This deletes releases, regardless of group. |
||
806 | * |
||
807 | * @void |
||
808 | * |
||
809 | * @throws \Exception |
||
810 | */ |
||
811 | public function deleteReleases(): void |
||
812 | { |
||
813 | $startTime = now()->toImmutable(); |
||
814 | $genres = new Genres(); |
||
815 | $passwordDeleted = $duplicateDeleted = $retentionDeleted = $completionDeleted = $disabledCategoryDeleted = 0; |
||
816 | $disabledGenreDeleted = $miscRetentionDeleted = $miscHashedDeleted = $categoryMinSizeDeleted = 0; |
||
817 | |||
818 | // Delete old releases and finished collections. |
||
819 | if ($this->echoCLI) { |
||
820 | $this->colorCLI->header('Process Releases -> Delete old releases and passworded releases.'); |
||
821 | } |
||
822 | |||
823 | // Releases past retention. |
||
824 | if ((int) Settings::settingValue('..releaseretentiondays') !== 0) { |
||
825 | $releases = Release::query()->where('postdate', '<', now()->subDays((int) Settings::settingValue('..releaseretentiondays')))->select(['id', 'guid'])->get(); |
||
826 | foreach ($releases as $release) { |
||
827 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
828 | $retentionDeleted++; |
||
829 | } |
||
830 | } |
||
831 | |||
832 | // Passworded releases. |
||
833 | if ((int) Settings::settingValue('..deletepasswordedrelease') === 1) { |
||
834 | $releases = Release::query()->join('release_files', 'release_files.releases_id', '=', 'releases.id')->select(['id', 'guid'])->where('release_files.passworded', '=', Releases::PASSWD_RAR)->orWhere('passwordstatus', '=', Releases::PASSWD_RAR)->get(); |
||
835 | foreach ($releases as $release) { |
||
836 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
837 | $passwordDeleted++; |
||
838 | } |
||
839 | } |
||
840 | |||
841 | if ((int) $this->crossPostTime !== 0) { |
||
842 | // Cross posted releases. |
||
843 | $releases = Release::query()->where('adddate', '>', now()->subHours($this->crossPostTime))->havingRaw('COUNT(name) > 1')->groupBy('name')->select(['id', 'guid'])->get(); |
||
844 | foreach ($releases as $release) { |
||
845 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
846 | $duplicateDeleted++; |
||
847 | } |
||
848 | } |
||
849 | |||
850 | if ($this->completion > 0) { |
||
851 | $releases = Release::query()->where('completion', '<', $this->completion)->where('completion', '>', 0)->select(['id', 'guid'])->get(); |
||
852 | foreach ($releases as $release) { |
||
853 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
854 | $completionDeleted++; |
||
855 | } |
||
856 | } |
||
857 | |||
858 | // Disabled categories. |
||
859 | $disabledCategories = Category::getDisabledIDs(); |
||
860 | if (\count($disabledCategories) > 0) { |
||
861 | foreach ($disabledCategories as $disabledCategory) { |
||
862 | $releases = Release::query()->where('categories_id', (int) $disabledCategory['id'])->select(['id', 'guid'])->get(); |
||
863 | foreach ($releases as $release) { |
||
864 | $disabledCategoryDeleted++; |
||
865 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
866 | } |
||
867 | } |
||
868 | } |
||
869 | |||
870 | // Delete smaller than category minimum sizes. |
||
871 | $categories = Category::query()->select(['id', 'minsizetoformrelease as minsize'])->get(); |
||
872 | |||
873 | foreach ($categories as $category) { |
||
874 | if ((int) $category->minsize > 0) { |
||
875 | $releases = Release::query()->where('categories_id', (int) $category->id)->where('size', '<', (int) $category->minsize)->select(['id', 'guid'])->limit(1000)->get(); |
||
876 | foreach ($releases as $release) { |
||
877 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
878 | $categoryMinSizeDeleted++; |
||
879 | } |
||
880 | } |
||
881 | } |
||
882 | |||
883 | // Disabled music genres. |
||
884 | $genrelist = $genres->getDisabledIDs(); |
||
885 | if (\count($genrelist) > 0) { |
||
886 | foreach ($genrelist as $genre) { |
||
887 | $musicInfoQuery = MusicInfo::query()->where('genre_id', (int) $genre['id'])->select(['id']); |
||
888 | $releases = Release::query() |
||
889 | ->joinSub($musicInfoQuery, 'mi', function ($join) { |
||
890 | $join->on('releases.musicinfo_id', '=', 'mi.id'); |
||
891 | }) |
||
892 | ->select(['releases.id', 'releases.guid']) |
||
893 | ->get(); |
||
894 | foreach ($releases as $release) { |
||
895 | $disabledGenreDeleted++; |
||
896 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
897 | } |
||
898 | } |
||
899 | } |
||
900 | |||
901 | // Misc other. |
||
902 | if (Settings::settingValue('..miscotherretentionhours') > 0) { |
||
903 | $releases = Release::query()->where('categories_id', Category::OTHER_MISC)->where('adddate', '<=', now()->subHours((int) Settings::settingValue('..miscotherretentionhours')))->select(['id', 'guid'])->get(); |
||
904 | foreach ($releases as $release) { |
||
905 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
906 | $miscRetentionDeleted++; |
||
907 | } |
||
908 | } |
||
909 | |||
910 | // Misc hashed. |
||
911 | if ((int) Settings::settingValue('..mischashedretentionhours') > 0) { |
||
912 | $releases = Release::query()->where('categories_id', Category::OTHER_HASHED)->where('adddate', '<=', now()->subHours((int) Settings::settingValue('..mischashedretentionhours')))->select(['id', 'guid'])->get(); |
||
913 | foreach ($releases as $release) { |
||
914 | $this->releases->deleteSingle(['g' => $release->guid, 'i' => $release->id], $this->nzb, $this->releaseImage); |
||
915 | $miscHashedDeleted++; |
||
916 | } |
||
917 | } |
||
918 | |||
919 | if ($this->echoCLI) { |
||
920 | $this->colorCLI->primary( |
||
921 | 'Removed releases: '. |
||
922 | number_format($retentionDeleted). |
||
923 | ' past retention, '. |
||
924 | number_format($passwordDeleted). |
||
925 | ' passworded, '. |
||
926 | number_format($duplicateDeleted). |
||
927 | ' crossposted, '. |
||
928 | number_format($disabledCategoryDeleted). |
||
929 | ' from disabled categories, '. |
||
930 | number_format($categoryMinSizeDeleted). |
||
931 | ' smaller than category settings, '. |
||
932 | number_format($disabledGenreDeleted). |
||
933 | ' from disabled music genres, '. |
||
934 | number_format($miscRetentionDeleted). |
||
935 | ' from misc->other '. |
||
936 | number_format($miscHashedDeleted). |
||
937 | ' from misc->hashed'. |
||
938 | ( |
||
939 | $this->completion > 0 |
||
940 | ? ', '.number_format($completionDeleted).' under '.$this->completion.'% completion.' |
||
941 | : '.' |
||
942 | ), |
||
943 | true |
||
944 | ); |
||
945 | |||
946 | $totalDeleted = ( |
||
947 | $retentionDeleted + $passwordDeleted + $duplicateDeleted + $disabledCategoryDeleted + |
||
948 | $disabledGenreDeleted + $miscRetentionDeleted + $miscHashedDeleted + $completionDeleted + |
||
949 | $categoryMinSizeDeleted |
||
950 | ); |
||
951 | if ($totalDeleted > 0) { |
||
952 | $totalTime = now()->diffInSeconds($startTime); |
||
953 | $this->colorCLI->primary( |
||
954 | 'Removed '.number_format($totalDeleted).' releases in '. |
||
955 | $totalTime.Str::plural(' second', $totalTime), |
||
956 | true |
||
957 | ); |
||
958 | } |
||
959 | } |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * Look if we have all the files in a collection (which have the file count in the subject). |
||
964 | * Set file check to complete. |
||
965 | * This means the the binary table has the same count as the file count in the subject, but |
||
966 | * the collection might not be complete yet since we might not have all the articles in the parts table. |
||
967 | * |
||
968 | * |
||
969 | * @void |
||
970 | * |
||
971 | * @throws \Throwable |
||
972 | */ |
||
973 | private function collectionFileCheckStage1(int $groupID): void |
||
990 | } |
||
991 | |||
992 | /** |
||
993 | * The first query sets filecheck to COLLFC_ZEROPART if there's a file that starts with 0 (ex. [00/100]). |
||
994 | * The second query sets filecheck to COLLFC_TEMPCOMP on everything left over, so anything that starts with 1 (ex. [01/100]). |
||
995 | * |
||
996 | * This is done because some collections start at 0 and some at 1, so if you were to assume the collection is complete |
||
997 | * at 0 then you would never get a complete collection if it starts with 1 and if it starts, you can end up creating |
||
998 | * a incomplete collection, since you assumed it was complete. |
||
999 | * |
||
1000 | * |
||
1001 | * @void |
||
1002 | * |
||
1003 | * @throws \Throwable |
||
1004 | */ |
||
1005 | private function collectionFileCheckStage2(int $groupID): void |
||
1006 | { |
||
1007 | DB::transaction(static function () use ($groupID) { |
||
1008 | $collectionsCheck = Collection::query()->select(['collections.id']) |
||
1009 | ->join('binaries', 'binaries.collections_id', '=', 'collections.id') |
||
1010 | ->where('binaries.filenumber', '=', 0) |
||
1011 | ->where('collections.totalfiles', '>', 0) |
||
1012 | ->where('collections.filecheck', '=', self::COLLFC_COMPCOLL); |
||
1013 | if (! empty($groupID)) { |
||
1014 | $collectionsCheck->where('collections.groups_id', $groupID); |
||
1015 | } |
||
1016 | $collectionsCheck->groupBy('collections.id'); |
||
1017 | |||
1018 | Collection::query()->joinSub($collectionsCheck, 'r', function ($join) { |
||
1019 | $join->on('collections.id', '=', 'r.id'); |
||
1020 | })->update(['collections.filecheck' => self::COLLFC_ZEROPART]); |
||
1021 | }, 10); |
||
1022 | |||
1023 | DB::transaction(static function () use ($groupID) { |
||
1024 | $collectionQuery = Collection::query()->where('filecheck', '=', self::COLLFC_COMPCOLL); |
||
1025 | if (! empty($groupID)) { |
||
1026 | $collectionQuery->where('groups_id', $groupID); |
||
1027 | } |
||
1028 | $collectionQuery->update(['filecheck' => self::COLLFC_TEMPCOMP]); |
||
1029 | }, 10); |
||
1030 | } |
||
1031 | |||
1032 | /** |
||
1033 | * Check if the files (binaries table) in a complete collection has all the parts. |
||
1034 | * If we have all the parts, set binaries table partcheck to FILE_COMPLETE. |
||
1035 | * |
||
1036 | * |
||
1037 | * @void |
||
1038 | * |
||
1039 | * @throws \Throwable |
||
1040 | */ |
||
1041 | private function collectionFileCheckStage3(string $where): void |
||
1042 | { |
||
1043 | DB::transaction(static function () use ($where) { |
||
1044 | DB::update( |
||
1045 | sprintf( |
||
1046 | ' |
||
1047 | UPDATE binaries b |
||
1048 | INNER JOIN |
||
1049 | ( |
||
1050 | SELECT b.id |
||
1051 | FROM binaries b |
||
1052 | INNER JOIN collections c ON c.id = b.collections_id |
||
1053 | WHERE c.filecheck = %d |
||
1054 | AND b.partcheck = %d %s |
||
1055 | AND b.currentparts = b.totalparts |
||
1056 | GROUP BY b.id, b.totalparts |
||
1057 | ) r ON b.id = r.id |
||
1058 | SET b.partcheck = %d', |
||
1059 | self::COLLFC_TEMPCOMP, |
||
1060 | self::FILE_INCOMPLETE, |
||
1061 | $where, |
||
1062 | self::FILE_COMPLETE |
||
1063 | ) |
||
1064 | ); |
||
1065 | }, 10); |
||
1066 | |||
1067 | DB::transaction(static function () use ($where) { |
||
1068 | DB::update( |
||
1069 | sprintf( |
||
1070 | ' |
||
1071 | UPDATE binaries b |
||
1072 | INNER JOIN |
||
1073 | ( |
||
1074 | SELECT b.id |
||
1075 | FROM binaries b |
||
1076 | INNER JOIN collections c ON c.id = b.collections_id |
||
1077 | WHERE c.filecheck = %d |
||
1078 | AND b.partcheck = %d %s |
||
1079 | AND b.currentparts >= (b.totalparts + 1) |
||
1080 | GROUP BY b.id, b.totalparts |
||
1081 | ) r ON b.id = r.id |
||
1082 | SET b.partcheck = %d', |
||
1083 | self::COLLFC_ZEROPART, |
||
1084 | self::FILE_INCOMPLETE, |
||
1085 | $where, |
||
1086 | self::FILE_COMPLETE |
||
1087 | ) |
||
1088 | ); |
||
1089 | }, 10); |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Check if all files (binaries table) for a collection are complete (if they all have the "parts"). |
||
1094 | * Set collections filecheck column to COLLFC_COMPPART. |
||
1095 | * This means the collection is complete. |
||
1096 | * |
||
1097 | * |
||
1098 | * @void |
||
1099 | * |
||
1100 | * @throws \Throwable |
||
1101 | */ |
||
1102 | private function collectionFileCheckStage4(string &$where): void |
||
1103 | { |
||
1104 | DB::transaction(static function () use ($where) { |
||
1105 | DB::update( |
||
1106 | sprintf( |
||
1107 | ' |
||
1108 | UPDATE collections c INNER JOIN |
||
1109 | (SELECT c.id FROM collections c |
||
1110 | INNER JOIN binaries b ON c.id = b.collections_id |
||
1111 | WHERE b.partcheck = 1 AND c.filecheck IN (%d, %d) %s |
||
1112 | GROUP BY b.collections_id, c.totalfiles, c.id HAVING COUNT(b.id) >= c.totalfiles) |
||
1113 | r ON c.id = r.id SET filecheck = %d', |
||
1114 | self::COLLFC_TEMPCOMP, |
||
1115 | self::COLLFC_ZEROPART, |
||
1116 | $where, |
||
1117 | self::COLLFC_COMPPART |
||
1118 | ) |
||
1119 | ); |
||
1120 | }, 10); |
||
1121 | } |
||
1122 | |||
1123 | /** |
||
1124 | * If not all files (binaries table) had their parts on the previous stage, |
||
1125 | * reset the collection filecheck column to COLLFC_COMPCOLL so we reprocess them next time. |
||
1126 | * |
||
1127 | * |
||
1128 | * @void |
||
1129 | * |
||
1130 | * @throws \Throwable |
||
1131 | */ |
||
1132 | private function collectionFileCheckStage5(int $groupId): void |
||
1133 | { |
||
1134 | DB::transaction(static function () use ($groupId) { |
||
1135 | $collectionQuery = Collection::query()->whereIn('filecheck', [self::COLLFC_TEMPCOMP, self::COLLFC_ZEROPART]); |
||
1136 | if (! empty($groupId)) { |
||
1137 | $collectionQuery->where('groups_id', $groupId); |
||
1138 | } |
||
1139 | $collectionQuery->update(['filecheck' => self::COLLFC_COMPCOLL]); |
||
1140 | }, 10); |
||
1141 | } |
||
1142 | |||
1143 | /** |
||
1144 | * If a collection did not have the file count (ie: [00/12]) or the collection is incomplete after |
||
1145 | * $this->collectionDelayTime hours, set the collection to complete to create it into a release/nzb. |
||
1146 | * |
||
1147 | * |
||
1148 | * @void |
||
1149 | * |
||
1150 | * @throws \Throwable |
||
1151 | */ |
||
1152 | private function collectionFileCheckStage6(string &$where): void |
||
1169 | } |
||
1170 | |||
1171 | /** |
||
1172 | * If a collection has been stuck for $this->collectionTimeout hours, delete it, it's bad. |
||
1173 | * |
||
1174 | * |
||
1175 | * @void |
||
1176 | * |
||
1177 | * @throws \Exception |
||
1178 | * @throws \Throwable |
||
1179 | */ |
||
1180 | private function processStuckCollections(int $groupID): void |
||
1181 | { |
||
1182 | $lastRun = Settings::settingValue('indexer.processing.last_run_time'); |
||
1195 | } |
||
1196 | } |
||
1197 |