From 457164a165a06ec15bd1e026f380b31b88371340 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Mon, 23 Feb 2026 17:13:05 +0100 Subject: [PATCH] Update par2 only releases handling --- app/Services/ReleaseProcessingService.php | 58 +---------------------- app/Services/ReleaseRemoverService.php | 14 ++++-- app/Support/DTOs/ReleaseDeleteStats.php | 7 +-- 3 files changed, 11 insertions(+), 68 deletions(-) diff --git a/app/Services/ReleaseProcessingService.php b/app/Services/ReleaseProcessingService.php index 0a397c012..9ed29dddd 100644 --- a/app/Services/ReleaseProcessingService.php +++ b/app/Services/ReleaseProcessingService.php @@ -431,7 +431,7 @@ final class ReleaseProcessingService ->where('c.filecheck', CollectionFileCheckStatus::Sized->value) ->where('c.filesize', '>', 0) ->groupBy('c.id') - ->havingRaw("COUNT(b.id) = SUM(CASE WHEN b.name REGEXP '\\\\.par2' THEN 1 ELSE 0 END)") + ->havingRaw("COUNT(b.id) = SUM(CASE WHEN b.name REGEXP '\\\\.(vol[0-9]+\\\\+[0-9]+\\\\.par2|par2)' THEN 1 ELSE 0 END)") ->pluck('c.id'); if ($par2OnlyCollectionIds->isNotEmpty()) { @@ -635,7 +635,6 @@ final class ReleaseProcessingService $stats = $this->deleteDisabledCategoryReleases($stats); $stats = $this->deleteCategoryMinSizeReleases($stats); $stats = $this->deleteDisabledGenreReleases($stats); - $stats = $this->deletePar2OnlyReleases($stats); $stats = $this->deleteMiscReleases($stats); $this->outputReleaseDeleteStats($stats, $startTime); @@ -1223,58 +1222,6 @@ final class ReleaseProcessingService return $stats; } - /** - * Delete releases that contain only PAR2 files (no actual content). - * - * PAR2-only releases are useless since they only contain repair/verification - * data without the original content files they are meant to repair. - * - * Two detection strategies are used: - * 1. The release name contains a .par2 filename pattern AND has no associated - * release_files (99% of par2-only releases have no inner file metadata). - * 2. All associated release_files have names containing .par2 (rare edge case - * where par2 metadata was stored during post-processing). - */ - private function deletePar2OnlyReleases(ReleaseDeleteStats $stats): ReleaseDeleteStats - { - // Strategy 1: Release name contains .par2 and has no release_files. - // This is the most common case — par2-only collections never have inner - // content files extracted, so release_files will be empty. - Release::query() - ->whereRaw("name REGEXP '\\\\.par2'") - ->whereNotExists(function ($query): void { - $query->select(DB::raw(1)) - ->from('release_files') - ->whereColumn('release_files.releases_id', 'releases.id'); - }) - ->select(['id', 'guid']) - ->chunkById(self::BATCH_SIZE, function ($releases) use (&$stats): bool { - foreach ($releases as $release) { - $this->deleteSingleRelease($release); - $stats = $stats->increment('par2Only'); - } - - return true; - }); - - // Strategy 2: All release_files entries are .par2 files. - // Catches releases that do have release_files, but every single one is a par2. - $par2OnlyByFiles = DB::select(" - SELECT r.id, r.guid - FROM releases r - INNER JOIN release_files rf ON r.id = rf.releases_id - GROUP BY r.id, r.guid - HAVING COUNT(*) = SUM(CASE WHEN rf.name REGEXP '\\\\.par2' THEN 1 ELSE 0 END) - "); - - foreach ($par2OnlyByFiles as $release) { - $this->deleteSingleRelease($release); - $stats = $stats->increment('par2Only'); - } - - return $stats; - } - private function deleteMiscReleases(ReleaseDeleteStats $stats): ReleaseDeleteStats { if ($this->settings->miscOtherRetentionHours > 0) { @@ -1533,9 +1480,6 @@ final class ReleaseProcessingService if ($stats->miscHashed > 0) { $this->outputStat('Misc->Hashed expired', $stats->miscHashed); } - if ($stats->par2Only > 0) { - $this->outputStat('Par2-only releases', $stats->par2Only); - } $this->outputStat('Total releases removed', $total); } else { diff --git a/app/Services/ReleaseRemoverService.php b/app/Services/ReleaseRemoverService.php index c711d02e9..93dda2ad5 100644 --- a/app/Services/ReleaseRemoverService.php +++ b/app/Services/ReleaseRemoverService.php @@ -869,8 +869,8 @@ class ReleaseRemoverService * data and cannot be used without the original content files. * * Two detection strategies are used: - * 1. The release name contains a .par2 filename pattern AND has no associated - * release_files (99% of par2-only releases have no inner file metadata). + * 1. The release name ends with a .par2 filename AND has no associated + * release_files, or only par2 release_files. * 2. All associated release_files have names containing .par2 (rare edge case * where par2 metadata was stored during post-processing). * @@ -878,12 +878,16 @@ class ReleaseRemoverService */ protected function removePar2Only(): bool|string { - // Strategy 1: Release name contains .par2 and has no release_files + // Strategy 1: Release name ends with a par2 filename pattern and has + // no non-par2 release_files. Matches .par2" (index) and .vol123+45.par2" (volumes). $this->executeSimpleRemoval('Par2Only', sprintf( "SELECT r.guid, r.searchname, r.id FROM releases r - WHERE r.name REGEXP '\\.par2' - AND r.id NOT IN (SELECT rf.releases_id FROM release_files rf) + WHERE r.name REGEXP '\\.(vol[0-9]+\\+[0-9]+\\.par2|par2)[\"\\' ]*$' + AND r.id NOT IN ( + SELECT rf.releases_id FROM release_files rf + WHERE rf.name NOT REGEXP '\\.par2' + ) %s", $this->crapTime )); diff --git a/app/Support/DTOs/ReleaseDeleteStats.php b/app/Support/DTOs/ReleaseDeleteStats.php index 58f535008..c4458218f 100644 --- a/app/Support/DTOs/ReleaseDeleteStats.php +++ b/app/Support/DTOs/ReleaseDeleteStats.php @@ -21,7 +21,6 @@ final readonly class ReleaseDeleteStats public int $disabledGenre = 0, public int $miscOther = 0, public int $miscHashed = 0, - public int $par2Only = 0, ) {} /** @@ -39,7 +38,6 @@ final readonly class ReleaseDeleteStats 'disabledGenre' => $this->disabledGenre, 'miscOther' => $this->miscOther, 'miscHashed' => $this->miscHashed, - 'par2Only' => $this->par2Only, ]; if (isset($values[$field])) { @@ -62,8 +60,7 @@ final readonly class ReleaseDeleteStats + $this->categoryMinSize + $this->disabledGenre + $this->miscOther - + $this->miscHashed - + $this->par2Only; + + $this->miscHashed; } /** @@ -83,7 +80,6 @@ final readonly class ReleaseDeleteStats 'disabledGenre' => $this->disabledGenre, 'miscOther' => $this->miscOther, 'miscHashed' => $this->miscHashed, - 'par2Only' => $this->par2Only, ]; } @@ -104,7 +100,6 @@ final readonly class ReleaseDeleteStats disabledGenre: $data['disabledGenre'] ?? 0, miscOther: $data['miscOther'] ?? 0, miscHashed: $data['miscHashed'] ?? 0, - par2Only: $data['par2Only'] ?? 0, ); } }