added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.
This commit is contained in:
184
app/Console/Commands/CheckUploadQueuesCommand.php
Normal file
184
app/Console/Commands/CheckUploadQueuesCommand.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Console\Concerns\InteractsWithCacheLocks;
|
||||
use App\Models\EventMediaAsset;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Cache\Lock;
|
||||
use Illuminate\Queue\QueueManager;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CheckUploadQueuesCommand extends Command
|
||||
{
|
||||
use InteractsWithCacheLocks;
|
||||
|
||||
protected $signature = 'storage:check-upload-queues {--force : Execute even if another health check is running}';
|
||||
|
||||
protected $description = 'Inspect upload-related queues and flag stalled or overloaded workers.';
|
||||
|
||||
public function handle(QueueManager $queueManager): int
|
||||
{
|
||||
$lockSeconds = (int) config('storage-monitor.queue_health.lock_seconds', 120);
|
||||
$lock = $this->acquireCommandLock('storage:queue-health', $lockSeconds, (bool) $this->option('force'));
|
||||
|
||||
if ($lock === false) {
|
||||
$this->warn('Queue health check already running.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$connection = config('queue.default');
|
||||
$thresholds = config('storage-monitor.queue_health.thresholds', []);
|
||||
if (empty($thresholds)) {
|
||||
$thresholds = [
|
||||
'default' => ['warning' => 100, 'critical' => 300],
|
||||
'media-storage' => ['warning' => 200, 'critical' => 500],
|
||||
'media-security' => ['warning' => 50, 'critical' => 150],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$queueSummaries = [];
|
||||
$alerts = [];
|
||||
|
||||
foreach ($thresholds as $queueName => $limits) {
|
||||
$size = $this->readQueueSize($queueManager, $connection, (string) $queueName);
|
||||
$failed = $this->countFailedJobs((string) $queueName);
|
||||
$severity = $this->determineQueueSeverity($size, $limits);
|
||||
|
||||
if ($severity !== 'ok') {
|
||||
$alerts[] = [
|
||||
'queue' => $queueName,
|
||||
'type' => 'size',
|
||||
'severity' => $severity,
|
||||
'size' => $size,
|
||||
];
|
||||
}
|
||||
|
||||
if ($failed > 0) {
|
||||
$alerts[] = [
|
||||
'queue' => $queueName,
|
||||
'type' => 'failed_jobs',
|
||||
'severity' => $failed >= 10 ? 'critical' : 'warning',
|
||||
'failed' => $failed,
|
||||
];
|
||||
}
|
||||
|
||||
$queueSummaries[] = [
|
||||
'queue' => $queueName,
|
||||
'size' => $size,
|
||||
'failed' => $failed,
|
||||
'severity' => $severity,
|
||||
'limits' => $limits,
|
||||
];
|
||||
}
|
||||
|
||||
$stalledMinutes = max(0, (int) config('storage-monitor.queue_health.stalled_minutes', 10));
|
||||
$stalledAssets = 0;
|
||||
if ($stalledMinutes > 0) {
|
||||
$stalledAssets = EventMediaAsset::query()
|
||||
->where('status', 'pending')
|
||||
->where('created_at', '<=', now()->subMinutes($stalledMinutes))
|
||||
->count();
|
||||
|
||||
if ($stalledAssets > 0) {
|
||||
$alerts[] = [
|
||||
'type' => 'pending_assets',
|
||||
'severity' => 'warning',
|
||||
'older_than_minutes' => $stalledMinutes,
|
||||
'count' => $stalledAssets,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$snapshot = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'connection' => $connection,
|
||||
'queues' => $queueSummaries,
|
||||
'alerts' => $alerts,
|
||||
'stalled_assets' => $stalledAssets,
|
||||
];
|
||||
|
||||
$cacheTtl = max(1, (int) config('storage-monitor.queue_health.cache_minutes', 10));
|
||||
Cache::put('storage:queue-health:last', $snapshot, now()->addMinutes($cacheTtl));
|
||||
|
||||
Log::channel('storage-jobs')->info('Upload queue health snapshot generated', [
|
||||
'queues' => count($queueSummaries),
|
||||
'alerts' => count($alerts),
|
||||
]);
|
||||
|
||||
$this->info(sprintf(
|
||||
'Checked %d queue(s); %d alert(s).',
|
||||
count($queueSummaries),
|
||||
count($alerts)
|
||||
));
|
||||
|
||||
return self::SUCCESS;
|
||||
} finally {
|
||||
if ($lock instanceof Lock) {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function readQueueSize(QueueManager $manager, ?string $connection, string $queue): int
|
||||
{
|
||||
try {
|
||||
return $manager->connection($connection)->size($queue);
|
||||
} catch (\Throwable $exception) {
|
||||
Log::channel('storage-jobs')->warning('Unable to read queue size', [
|
||||
'queue' => $queue,
|
||||
'connection' => $connection,
|
||||
'message' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private function countFailedJobs(string $queue): int
|
||||
{
|
||||
$table = config('queue.failed.table', 'failed_jobs');
|
||||
|
||||
if (! $this->failedJobsTableExists($table)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) DB::table($table)->where('queue', $queue)->count();
|
||||
}
|
||||
|
||||
private function failedJobsTableExists(string $table): bool
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
if (array_key_exists($table, $cache)) {
|
||||
return $cache[$table];
|
||||
}
|
||||
|
||||
return $cache[$table] = Schema::hasTable($table);
|
||||
}
|
||||
|
||||
private function determineQueueSeverity(int $size, array $limits): string
|
||||
{
|
||||
if ($size < 0) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
$critical = (int) ($limits['critical'] ?? 0);
|
||||
$warning = (int) ($limits['warning'] ?? 0);
|
||||
|
||||
if ($critical > 0 && $size >= $critical) {
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
if ($warning > 0 && $size >= $warning) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
}
|
||||
42
app/Console/Commands/DeactivateExpiredPhotoboothAccounts.php
Normal file
42
app/Console/Commands/DeactivateExpiredPhotoboothAccounts.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Services\Photobooth\PhotoboothProvisioner;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeactivateExpiredPhotoboothAccounts extends Command
|
||||
{
|
||||
protected $signature = 'photobooth:cleanup-expired';
|
||||
|
||||
protected $description = 'Disable Photobooth FTP accounts that have passed their expiry date.';
|
||||
|
||||
public function handle(PhotoboothProvisioner $provisioner): int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
Event::query()
|
||||
->where('photobooth_enabled', true)
|
||||
->whereNotNull('photobooth_expires_at')
|
||||
->where('photobooth_expires_at', '<=', now())
|
||||
->chunkById(50, function ($events) use (&$total, $provisioner) {
|
||||
foreach ($events as $event) {
|
||||
try {
|
||||
$provisioner->disable($event);
|
||||
$total++;
|
||||
} catch (\Throwable $exception) {
|
||||
Log::error('Failed to disable expired photobooth account', [
|
||||
'event_id' => $event->id,
|
||||
'message' => $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$this->info(sprintf('Photobooth cleanup complete (%d accounts disabled).', $total));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
106
app/Console/Commands/DispatchStorageArchiveCommand.php
Normal file
106
app/Console/Commands/DispatchStorageArchiveCommand.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
194
app/Console/Commands/MonitorStorageCommand.php
Normal file
194
app/Console/Commands/MonitorStorageCommand.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Console\Concerns\InteractsWithCacheLocks;
|
||||
use App\Models\EventMediaAsset;
|
||||
use App\Models\MediaStorageTarget;
|
||||
use App\Services\Storage\StorageHealthService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Cache\Lock;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class MonitorStorageCommand extends Command
|
||||
{
|
||||
use InteractsWithCacheLocks;
|
||||
|
||||
protected $signature = 'storage:monitor {--force : Execute even if another run is still in progress}';
|
||||
|
||||
protected $description = 'Collect storage capacity statistics and cache them for dashboards & alerts.';
|
||||
|
||||
public function __construct(private readonly StorageHealthService $storageHealth)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$lockSeconds = (int) config('storage-monitor.monitor.lock_seconds', 300);
|
||||
$lock = $this->acquireCommandLock('storage:monitor', $lockSeconds, (bool) $this->option('force'));
|
||||
|
||||
if ($lock === false) {
|
||||
$this->warn('storage:monitor is already running on another worker.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
$targets = MediaStorageTarget::active()->get();
|
||||
|
||||
if ($targets->isEmpty()) {
|
||||
$this->info('No media storage targets available.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$assetStats = $this->buildAssetStatistics();
|
||||
$thresholds = $this->capacityThresholds();
|
||||
$alerts = [];
|
||||
$snapshotTargets = [];
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$capacity = $this->storageHealth->getCapacity($target);
|
||||
$assets = $assetStats[$target->id] ?? [
|
||||
'total' => 0,
|
||||
'bytes' => 0,
|
||||
'by_status' => [],
|
||||
];
|
||||
|
||||
$severity = $this->determineCapacitySeverity($capacity, $thresholds);
|
||||
if ($severity !== 'ok') {
|
||||
$alerts[] = [
|
||||
'target' => $target->key,
|
||||
'type' => 'capacity',
|
||||
'severity' => $severity,
|
||||
'percentage' => $capacity['percentage'] ?? null,
|
||||
'status' => $capacity['status'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
$failedCount = $assets['by_status']['failed']['count'] ?? 0;
|
||||
if ($failedCount > 0) {
|
||||
$alerts[] = [
|
||||
'target' => $target->key,
|
||||
'type' => 'failed_assets',
|
||||
'severity' => 'warning',
|
||||
'failed' => $failedCount,
|
||||
];
|
||||
}
|
||||
|
||||
$snapshotTargets[] = [
|
||||
'id' => $target->id,
|
||||
'key' => $target->key,
|
||||
'name' => $target->name,
|
||||
'is_hot' => (bool) $target->is_hot,
|
||||
'capacity' => $capacity,
|
||||
'assets' => $assets,
|
||||
];
|
||||
}
|
||||
|
||||
$snapshot = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'targets' => $snapshotTargets,
|
||||
'alerts' => $alerts,
|
||||
];
|
||||
|
||||
$ttlMinutes = max(1, (int) config('storage-monitor.monitor.cache_minutes', 15));
|
||||
Cache::put('storage:monitor:last', $snapshot, now()->addMinutes($ttlMinutes));
|
||||
|
||||
Log::channel('storage-jobs')->info('Storage monitor snapshot generated', [
|
||||
'targets' => count($snapshotTargets),
|
||||
'alerts' => count($alerts),
|
||||
]);
|
||||
|
||||
$this->info(sprintf(
|
||||
'Storage monitor finished: %d targets, %d alerts.',
|
||||
count($snapshotTargets),
|
||||
count($alerts)
|
||||
));
|
||||
|
||||
return self::SUCCESS;
|
||||
} finally {
|
||||
if ($lock instanceof Lock) {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array>
|
||||
*/
|
||||
private function buildAssetStatistics(): array
|
||||
{
|
||||
return EventMediaAsset::query()
|
||||
->selectRaw('media_storage_target_id, status, COUNT(*) as total_count, COALESCE(SUM(size_bytes), 0) as total_bytes')
|
||||
->groupBy('media_storage_target_id', 'status')
|
||||
->get()
|
||||
->groupBy('media_storage_target_id')
|
||||
->map(function ($rows) {
|
||||
$byStatus = [];
|
||||
$totalCount = 0;
|
||||
$totalBytes = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$count = (int) ($row->total_count ?? 0);
|
||||
$bytes = (int) ($row->total_bytes ?? 0);
|
||||
|
||||
$totalCount += $count;
|
||||
$totalBytes += $bytes;
|
||||
$byStatus[$row->status] = [
|
||||
'count' => $count,
|
||||
'bytes' => $bytes,
|
||||
];
|
||||
}
|
||||
|
||||
ksort($byStatus);
|
||||
|
||||
return [
|
||||
'total' => $totalCount,
|
||||
'bytes' => $totalBytes,
|
||||
'by_status' => $byStatus,
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function capacityThresholds(): array
|
||||
{
|
||||
$warning = (int) config('storage-monitor.capacity_thresholds.warning', 75);
|
||||
$critical = (int) config('storage-monitor.capacity_thresholds.critical', 90);
|
||||
|
||||
if ($warning > $critical) {
|
||||
[$warning, $critical] = [$critical, $warning];
|
||||
}
|
||||
|
||||
return [
|
||||
'warning' => $warning,
|
||||
'critical' => $critical,
|
||||
];
|
||||
}
|
||||
|
||||
private function determineCapacitySeverity(array $capacity, array $thresholds): string
|
||||
{
|
||||
$status = $capacity['status'] ?? 'ok';
|
||||
if ($status !== 'ok') {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$percentage = $capacity['percentage'] ?? null;
|
||||
if ($percentage === null) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
if ($percentage >= ($thresholds['critical'] ?? 95)) {
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
if ($percentage >= ($thresholds['warning'] ?? 75)) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
}
|
||||
51
app/Console/Commands/PhotoboothIngestCommand.php
Normal file
51
app/Console/Commands/PhotoboothIngestCommand.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Services\Photobooth\PhotoboothIngestService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PhotoboothIngestCommand extends Command
|
||||
{
|
||||
protected $signature = 'photobooth:ingest {--event= : Restrict ingestion to a single event ID}
|
||||
{--max-files= : Maximum files to import per event in this run}';
|
||||
|
||||
protected $description = 'Ingest pending Photobooth uploads from the FTP mount into event storage.';
|
||||
|
||||
public function handle(PhotoboothIngestService $ingestService): int
|
||||
{
|
||||
$eventId = $this->option('event');
|
||||
$maxFiles = $this->option('max-files');
|
||||
$processedTotal = 0;
|
||||
$skippedTotal = 0;
|
||||
|
||||
$query = Event::query()
|
||||
->where('photobooth_enabled', true)
|
||||
->whereNotNull('photobooth_path');
|
||||
|
||||
if ($eventId) {
|
||||
$query->whereKey($eventId);
|
||||
}
|
||||
|
||||
$query->chunkById(25, function ($events) use ($ingestService, $maxFiles, &$processedTotal, &$skippedTotal) {
|
||||
foreach ($events as $event) {
|
||||
$summary = $ingestService->ingest($event, $maxFiles ? (int) $maxFiles : null);
|
||||
$processedTotal += $summary['processed'] ?? 0;
|
||||
$skippedTotal += $summary['skipped'] ?? 0;
|
||||
|
||||
$this->line(sprintf(
|
||||
'Event #%d (%s): %d imported, %d skipped',
|
||||
$event->id,
|
||||
$event->slug ?? 'event',
|
||||
$summary['processed'] ?? 0,
|
||||
$summary['skipped'] ?? 0
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
$this->info(sprintf('Photobooth ingest finished. Processed: %d, Skipped: %d', $processedTotal, $skippedTotal));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
28
app/Console/Commands/SyncHelpCenter.php
Normal file
28
app/Console/Commands/SyncHelpCenter.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Help\HelpSyncService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncHelpCenter extends Command
|
||||
{
|
||||
protected $signature = 'help:sync';
|
||||
|
||||
protected $description = 'Compile markdown help articles into cached JSON bundles.';
|
||||
|
||||
public function handle(HelpSyncService $service): int
|
||||
{
|
||||
$result = $service->sync();
|
||||
|
||||
foreach ($result as $audience => $locales) {
|
||||
foreach ($locales as $locale => $count) {
|
||||
$this->components->info(sprintf('Synced %d %s/%s articles', $count, $audience, $locale));
|
||||
}
|
||||
}
|
||||
|
||||
$this->components->success('Help center cache updated.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
40
app/Console/Concerns/InteractsWithCacheLocks.php
Normal file
40
app/Console/Concerns/InteractsWithCacheLocks.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Cache\Lock;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
trait InteractsWithCacheLocks
|
||||
{
|
||||
/**
|
||||
* Attempt to acquire a cache-backed lock for the given command.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Cache\Lock|false|null Returns the lock when acquired, false if another process holds it, null if locks are unsupported.
|
||||
*/
|
||||
protected function acquireCommandLock(string $name, int $seconds, bool $force = false): Lock|false|null
|
||||
{
|
||||
try {
|
||||
$lock = Cache::lock($name, $seconds);
|
||||
|
||||
if ($force) {
|
||||
$lock->forceRelease();
|
||||
}
|
||||
|
||||
if ($lock->get()) {
|
||||
return $lock;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\BadMethodCallException $exception) {
|
||||
Log::channel('storage-jobs')->debug('Cache store does not support locks for command', [
|
||||
'lock' => $name,
|
||||
'store' => config('cache.default'),
|
||||
'exception' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user