$id, 'tipo' => $tipo, 'status' => 'queued', 'fase' => $fase, 'total' => $total, 'processed' => 0, 'logs' => [], 'result' => [], 'error' => null, 'started_at' => null, 'finished_at' => null, ]; $this->put($id, $state); Cache::put(self::CHIAVE_CORRENTE, $id, self::TTL_CORRENTE); return $state; } /** * Id dell'operazione in corso, o null se non ce n'e' nessuna. * * Serve a due cose: rifiutare l'avvio di una seconda operazione, e * riagganciare la barra di avanzamento quando la pagina viene ricaricata — * senza, un azzeramento fermo in attesa di conferma resterebbe irraggiungibile * e nessuno potrebbe piu' confermarlo o annullarlo. */ public function corrente(): ?string { $id = Cache::get(self::CHIAVE_CORRENTE); if (! is_string($id) || $id === '') { return null; } $stato = $this->get($id); // Puntatore orfano (stato scaduto) o operazione gia' chiusa: il posto // e' libero, e il puntatore va tolto di mezzo. if ($stato === null || in_array($stato['status'] ?? '', ['completed', 'failed'], true)) { Cache::forget(self::CHIAVE_CORRENTE); return null; } return $id; } public function get(string $id): ?array { return Cache::get($this->key($id)); } public function put(string $id, array $state): void { Cache::put($this->key($id), $state, $this->ttlSeconds); } public function forget(string $id): void { Cache::forget($this->key($id)); } public function update(string $id, callable $callback): ?array { $state = $this->get($id); if (! $state) { return null; } $state = $callback($state) ?? $state; $this->put($id, $state); return $state; } public function log(string $id, string $message): void { $this->update($id, function (array $state) use ($message) { $state['logs'][] = '[' . now()->format('H:i:s') . '] ' . $message; $state['logs'] = array_slice($state['logs'], -self::MAX_LOG); return $state; }); } /** * Cambia fase e, insieme, il totale di riferimento: ogni fase ha una sua * unita' di misura (file da comprimere, file da estrarre, passi di * cancellazione) e la barra riparte da zero su quel totale. */ public function fase(string $id, string $fase, int $total = 0): void { $this->update($id, function (array $state) use ($fase, $total) { $state['fase'] = $fase; $state['total'] = $total; $state['processed'] = 0; return $state; }); $this->log($id, $fase . ($total > 0 ? ' (' . $total . ')' : '')); } public function avanza(string $id, int $processed, ?int $total = null): void { $this->update($id, function (array $state) use ($processed, $total) { $state['processed'] = $processed; if ($total !== null) { $state['total'] = $total; } return $state; }); } public function markRunning(string $id): void { $this->update($id, function (array $state) { $state['status'] = 'running'; $state['started_at'] ??= now()->toDateTimeString(); return $state; }); } public function markCompleted(string $id, array $result = []): void { $this->update($id, function (array $state) use ($result) { $state['status'] = 'completed'; $state['processed'] = max($state['processed'], $state['total']); $state['result'] = $result + $state['result']; $state['finished_at'] = now()->toDateTimeString(); return $state; }); $this->liberaCorrente($id); } public function markFailed(string $id, string $error): void { $this->update($id, function (array $state) use ($error) { $state['status'] = 'failed'; $state['error'] = $error; $state['finished_at'] = now()->toDateTimeString(); return $state; }); $this->log($id, 'ERRORE: ' . $error); $this->liberaCorrente($id); } /** * Libera il posto, ma solo se e' ancora questa operazione a occuparlo: * senza il confronto, la chiusura di un'operazione vecchia sbloccherebbe * quella appena avviata da qualcun altro. */ private function liberaCorrente(string $id): void { if (Cache::get(self::CHIAVE_CORRENTE) === $id) { Cache::forget(self::CHIAVE_CORRENTE); } } /** * L'archivio e' pronto ma non si prosegue: l'azzeramento parte solo quando * il download risulta consegnato per intero. */ public function markAttesaDownload(string $id, array $result): void { $this->update($id, function (array $state) use ($result) { $state['status'] = 'running'; $state['fase'] = self::FASE_ATTESA_DOWNLOAD; $state['processed'] = $state['total']; $state['result'] = $result + $state['result']; return $state; }); } protected function key(string $id): string { return 'backup-op:' . $id; } }