markRunning($this->operationId); $stato->fase($this->operationId, 'Lettura dei dati gestionali'); $destinazione = self::percorso($this->operationId); try { $this->rimuoviArchiviVecchi($archivio); $xml = $exchange->currentXmlBackup(); $datiPiantine = $piantine->esporta(); $stato->fase($this->operationId, 'Compressione dei file'); $archivio->create( $xml, $datiPiantine, $destinazione, fn (int $fatti, int $totale) => $stato->avanza($this->operationId, $fatti, $totale) ); } catch (\Throwable $e) { Log::error('Creazione del backup non riuscita.', ['exception' => $e]); $stato->markFailed($this->operationId, $e->getMessage()); return; } $risultato = [ 'file' => basename($destinazione), 'nome_download' => 'termanager2-backup-' . now()->format('Ymd-His') . '.zip', 'dimensione' => filesize($destinazione) ?: 0, ]; if ($this->perAzzeramento) { $stato->log($this->operationId, 'Archivio pronto. In attesa che il download sia completato.'); $stato->markAttesaDownload($this->operationId, $risultato); return; } $stato->log($this->operationId, 'Backup pronto per il download.'); $stato->markCompleted($this->operationId, $risultato); } /** * Chiamato da Laravel quando il job muore per cause che handle() non puo' * intercettare: il timeout del worker (che uccide il processo), un errore * fatale, una dipendenza non risolvibile. * * Senza, l'operazione resterebbe "in corso" e terrebbe occupato il posto * unico fino alla scadenza del puntatore: un'ora in cui nessuno puo' * avviare un backup e la pagina mostra una barra che non avanzera' mai. */ public function failed(?\Throwable $e): void { app(BackupOperationState::class)->markFailed( $this->operationId, $e?->getMessage() ?: 'Creazione del backup interrotta (timeout o errore del worker).' ); @unlink(self::percorso($this->operationId)); } public static function percorso(string $operationId): string { return storage_path('app/backups/' . $operationId . '.zip'); } /** * Gli archivi restano su disco finche' il browser non li scarica, quindi un * download mai completato lascerebbe un file per sempre. Si ripulisce * all'inizio di ogni nuovo backup invece di aggiungere un task pianificato: * il momento in cui serve spazio e' esattamente questo. */ private function rimuoviArchiviVecchi(BackupArchive $archivio): void { $cartella = storage_path('app/backups'); if (! is_dir($cartella)) { return; } $limite = now()->subDay()->getTimestamp(); foreach (glob($cartella . '/*.zip') ?: [] as $file) { if (filemtime($file) < $limite) { @unlink($file); } } } }