107 lines
3.9 KiB
PHP
107 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Console\Concerns\InteractsWithCacheLocks;
|
|
use App\Jobs\ArchiveEventMediaAssets;
|
|
use App\Models\Event;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Contracts\Cache\Lock;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class DispatchStorageArchiveCommand extends Command
|
|
{
|
|
use InteractsWithCacheLocks;
|
|
|
|
protected $signature = 'storage:archive-pending
|
|
{--event= : Limit processing to a specific event ID}
|
|
{--force : Run even if another dispatcher instance is active}';
|
|
|
|
protected $description = 'Queue archive jobs for events whose galleries expired or were manually archived.';
|
|
|
|
public function handle(): int
|
|
{
|
|
$lockSeconds = (int) config('storage-monitor.archive.lock_seconds', 1800);
|
|
$lock = $this->acquireCommandLock('storage:archive-dispatcher', $lockSeconds, (bool) $this->option('force'));
|
|
|
|
if ($lock === false) {
|
|
$this->warn('Another archive dispatcher run is already executing.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$eventLockTtl = (int) config('storage-monitor.archive.event_lock_seconds', 3600);
|
|
$graceDays = max(0, (int) config('storage-monitor.archive.grace_days', 3));
|
|
$cutoff = now()->subDays($graceDays);
|
|
$chunkSize = max(1, (int) config('storage-monitor.archive.chunk', 25));
|
|
$maxDispatch = max(1, (int) config('storage-monitor.archive.max_dispatch', 100));
|
|
$eventId = $this->option('event');
|
|
$dispatched = 0;
|
|
|
|
try {
|
|
$query = Event::query()
|
|
->with('eventPackages:id,event_id,gallery_expires_at')
|
|
->whereHas('mediaAssets', function ($builder) {
|
|
$builder->where('status', '!=', 'archived');
|
|
});
|
|
|
|
if ($eventId) {
|
|
$query->whereKey($eventId);
|
|
} else {
|
|
$query->where(function ($builder) use ($cutoff) {
|
|
$builder->where('status', 'archived')
|
|
->orWhereHas('eventPackages', function ($packages) use ($cutoff) {
|
|
$packages->whereNotNull('gallery_expires_at')
|
|
->where('gallery_expires_at', '<=', $cutoff);
|
|
});
|
|
});
|
|
}
|
|
|
|
$query->chunkById($chunkSize, function ($events) use (&$dispatched, $maxDispatch, $eventLockTtl) {
|
|
foreach ($events as $event) {
|
|
if ($dispatched >= $maxDispatch) {
|
|
return false;
|
|
}
|
|
|
|
$eventLock = $this->acquireCommandLock('storage:archive-event-'.$event->id, $eventLockTtl);
|
|
if ($eventLock === false) {
|
|
Log::channel('storage-jobs')->info('Archive dispatch skipped due to in-flight lock', [
|
|
'event_id' => $event->id,
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
ArchiveEventMediaAssets::dispatch($event->id);
|
|
$dispatched++;
|
|
|
|
Log::channel('storage-jobs')->info('Archive job dispatched', [
|
|
'event_id' => $event->id,
|
|
'queue' => 'media-storage',
|
|
]);
|
|
} finally {
|
|
if ($eventLock instanceof Lock) {
|
|
$eventLock->release();
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
$this->info(sprintf('Dispatched %d archive job(s).', $dispatched));
|
|
Log::channel('storage-jobs')->info('Archive dispatch run finished', [
|
|
'dispatched' => $dispatched,
|
|
'event_limit' => $eventId,
|
|
]);
|
|
|
|
return self::SUCCESS;
|
|
} finally {
|
|
if ($lock instanceof Lock) {
|
|
$lock->release();
|
|
}
|
|
}
|
|
}
|
|
}
|