Update par2 only releases handling

This commit is contained in:
DariusIII
2026-02-23 17:13:05 +01:00
parent 40af42f00e
commit 457164a165
3 changed files with 11 additions and 68 deletions
+1 -57
View File
@@ -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 {
+9 -5
View File
@@ -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
));
+1 -6
View File
@@ -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,
);
}
}