mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-29 01:08:56 +00:00
193 lines
5.8 KiB
PHP
193 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Binaries;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Handles header storage transactions and rollback cleanup.
|
|
*/
|
|
final class HeaderStorageTransaction
|
|
{
|
|
private const MAX_SQL_ROWS_PER_STATEMENT = 500;
|
|
|
|
private CollectionHandler $collectionHandler;
|
|
|
|
private BinaryHandler $binaryHandler;
|
|
|
|
private string $batchNoise;
|
|
|
|
private bool $hadErrors = false;
|
|
|
|
public function __construct(
|
|
CollectionHandler $collectionHandler,
|
|
BinaryHandler $binaryHandler,
|
|
PartHandler $partHandler
|
|
) {
|
|
$this->collectionHandler = $collectionHandler;
|
|
$this->binaryHandler = $binaryHandler;
|
|
$this->batchNoise = bin2hex(random_bytes(8));
|
|
}
|
|
|
|
/**
|
|
* Get the batch noise marker for this transaction.
|
|
*/
|
|
public function getBatchNoise(): string
|
|
{
|
|
return $this->batchNoise;
|
|
}
|
|
|
|
/**
|
|
* Start a new database transaction.
|
|
*/
|
|
public function begin(): void
|
|
{
|
|
DB::beginTransaction();
|
|
$this->hadErrors = false;
|
|
}
|
|
|
|
/**
|
|
* Mark that an error occurred.
|
|
*/
|
|
public function markError(): void
|
|
{
|
|
$this->hadErrors = true;
|
|
}
|
|
|
|
/**
|
|
* Check if errors occurred.
|
|
*/
|
|
public function hasErrors(): bool
|
|
{
|
|
return $this->hadErrors;
|
|
}
|
|
|
|
/**
|
|
* Commit the transaction if no errors, rollback otherwise.
|
|
*/
|
|
public function finish(): bool
|
|
{
|
|
if ($this->hadErrors) {
|
|
$this->rollbackAndCleanup();
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
DB::commit();
|
|
|
|
return true;
|
|
} catch (\Throwable $e) {
|
|
$this->rollbackAndCleanup();
|
|
|
|
if (config('app.debug') === true) {
|
|
Log::error('HeaderStorageTransaction commit failed: '.$e->getMessage());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform rollback and cleanup any orphaned data.
|
|
*/
|
|
private function rollbackAndCleanup(): void
|
|
{
|
|
try {
|
|
DB::rollBack();
|
|
} catch (\Throwable $e) {
|
|
// Already rolled back
|
|
}
|
|
|
|
// InnoDB rollback already reverts all rows written by this transaction.
|
|
// Manual cleanup is retained only for SQLite test/in-memory scenarios.
|
|
if (DB::getDriverName() === 'sqlite') {
|
|
$this->cleanup();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup rows that may have been inserted before rollback.
|
|
*/
|
|
private function cleanup(): void
|
|
{
|
|
try {
|
|
$this->cleanupParts();
|
|
$this->cleanupBinaries();
|
|
$this->cleanupCollections();
|
|
} catch (\Throwable $e) {
|
|
if (config('app.debug') === true) {
|
|
Log::warning('Post-rollback cleanup failed: '.$e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private function cleanupParts(): void
|
|
{
|
|
$ids = $this->binaryHandler->getInsertedIds();
|
|
foreach (array_chunk($ids, self::MAX_SQL_ROWS_PER_STATEMENT) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, \count($chunk), '?'));
|
|
DB::statement("DELETE FROM parts WHERE binaries_id IN ({$placeholders})", $chunk);
|
|
}
|
|
}
|
|
|
|
private function cleanupBinaries(): void
|
|
{
|
|
$ids = $this->binaryHandler->getInsertedIds();
|
|
foreach (array_chunk($ids, self::MAX_SQL_ROWS_PER_STATEMENT) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, \count($chunk), '?'));
|
|
DB::statement("DELETE FROM binaries WHERE id IN ({$placeholders})", $chunk);
|
|
}
|
|
}
|
|
|
|
private function cleanupCollections(): void
|
|
{
|
|
$insertedIds = $this->collectionHandler->getInsertedIds();
|
|
$allIds = $this->collectionHandler->getAllIds();
|
|
$hashes = $this->collectionHandler->getBatchHashes();
|
|
|
|
$ids = ! empty($insertedIds) ? $insertedIds : $allIds;
|
|
|
|
if (! empty($ids)) {
|
|
foreach (array_chunk($ids, self::MAX_SQL_ROWS_PER_STATEMENT) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, \count($chunk), '?'));
|
|
|
|
DB::statement(
|
|
"DELETE FROM parts WHERE binaries_id IN (SELECT id FROM binaries WHERE collections_id IN ({$placeholders}))",
|
|
$chunk
|
|
);
|
|
DB::statement("DELETE FROM binaries WHERE collections_id IN ({$placeholders})", $chunk);
|
|
DB::statement("DELETE FROM collections WHERE id IN ({$placeholders})", $chunk);
|
|
}
|
|
} elseif (! empty($hashes)) {
|
|
foreach (array_chunk($hashes, self::MAX_SQL_ROWS_PER_STATEMENT) as $chunk) {
|
|
$placeholders = implode(',', array_fill(0, \count($chunk), '?'));
|
|
|
|
DB::statement(
|
|
"DELETE FROM parts WHERE binaries_id IN (SELECT id FROM binaries WHERE collections_id IN (SELECT id FROM collections WHERE collectionhash IN ({$placeholders})))",
|
|
$chunk
|
|
);
|
|
DB::statement(
|
|
"DELETE FROM binaries WHERE collections_id IN (SELECT id FROM collections WHERE collectionhash IN ({$placeholders}))",
|
|
$chunk
|
|
);
|
|
DB::statement("DELETE FROM collections WHERE collectionhash IN ({$placeholders})", $chunk);
|
|
}
|
|
} else {
|
|
// Fallback by noise marker
|
|
DB::statement(
|
|
'DELETE FROM parts WHERE binaries_id IN (SELECT b.id FROM binaries b WHERE b.collections_id IN (SELECT c.id FROM collections c WHERE c.noise = ?))',
|
|
[$this->batchNoise]
|
|
);
|
|
DB::statement(
|
|
'DELETE FROM binaries WHERE collections_id IN (SELECT id FROM collections WHERE noise = ?)',
|
|
[$this->batchNoise]
|
|
);
|
|
DB::statement('DELETE FROM collections WHERE noise = ?', [$this->batchNoise]);
|
|
}
|
|
}
|
|
}
|