85 lines
2.6 KiB
PHP
85 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Uploads;
|
|
|
|
use App\Repositories\Uploads\UploadSessionRepository;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\File;
|
|
use RuntimeException;
|
|
|
|
final class UploadCancelService
|
|
{
|
|
public function __construct(
|
|
private readonly UploadSessionRepository $sessions,
|
|
private readonly UploadStorageService $storage,
|
|
private readonly UploadAuditService $audit
|
|
) {
|
|
}
|
|
|
|
public function cancel(string $sessionId, int $userId, string $ip): array
|
|
{
|
|
$session = $this->sessions->getOrFail($sessionId);
|
|
|
|
$lockSeconds = (int) config('uploads.chunk.lock_seconds', 10);
|
|
$lockWait = (int) config('uploads.chunk.lock_wait_seconds', 5);
|
|
$lock = Cache::lock('uploads:cancel:' . $sessionId, $lockSeconds);
|
|
|
|
try {
|
|
$lock->block($lockWait);
|
|
} catch (\Throwable $e) {
|
|
$this->audit->log($userId, 'upload_cancel_locked', $ip, [
|
|
'session_id' => $sessionId,
|
|
]);
|
|
throw new RuntimeException('Upload is busy. Please retry.');
|
|
}
|
|
|
|
try {
|
|
if (in_array($session->status, [UploadSessionStatus::CANCELLED, UploadSessionStatus::PROCESSED, UploadSessionStatus::QUARANTINED], true)) {
|
|
$this->audit->log($userId, 'upload_cancel_noop', $ip, [
|
|
'session_id' => $sessionId,
|
|
'status' => $session->status,
|
|
]);
|
|
|
|
return [
|
|
'session_id' => $sessionId,
|
|
'status' => $session->status,
|
|
];
|
|
}
|
|
|
|
$this->safeDeleteTmp($session->tempPath);
|
|
|
|
$this->sessions->updateStatus($sessionId, UploadSessionStatus::CANCELLED);
|
|
$this->sessions->updateProgress($sessionId, 0);
|
|
$this->sessions->updateFailureReason($sessionId, 'cancelled');
|
|
|
|
$this->audit->log($userId, 'upload_cancelled', $ip, [
|
|
'session_id' => $sessionId,
|
|
]);
|
|
|
|
return [
|
|
'session_id' => $sessionId,
|
|
'status' => UploadSessionStatus::CANCELLED,
|
|
];
|
|
} finally {
|
|
optional($lock)->release();
|
|
}
|
|
}
|
|
|
|
private function safeDeleteTmp(string $path): void
|
|
{
|
|
$tmpRoot = $this->storage->sectionPath('tmp');
|
|
$realRoot = realpath($tmpRoot);
|
|
$realPath = realpath($path);
|
|
|
|
if (! $realRoot || ! $realPath || strpos($realPath, $realRoot) !== 0) {
|
|
return;
|
|
}
|
|
|
|
if (File::exists($realPath)) {
|
|
File::delete($realPath);
|
|
}
|
|
}
|
|
}
|