Files
termanager2/app/Jobs/CreateBackupArchive.php
2026-07-29 14:26:36 +02:00

118 lines
3.8 KiB
PHP

<?php
namespace App\Jobs;
use App\Livewire\Settings\XmlExchange;
use App\Services\BackupArchive;
use App\Services\BackupOperationState;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
/**
* Costruisce il pacchetto di backup in coda.
*
* In coda e non nella richiesta perche' un'installazione con qualche centinaio
* di PDF supera comodamente i 300 secondi di `fastcgi_read_timeout`: il browser
* si vedrebbe chiudere la connessione a meta' compressione, senza modo di
* distinguerlo da un errore.
*/
class CreateBackupArchive implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public int $tries = 1;
/** Sotto il --timeout=600 del worker: se sfora, e' il worker a ucciderlo. */
public int $timeout = 570;
/**
* @param bool $perAzzeramento Quando true il job non chiude l'operazione
* ma la lascia in attesa del download: e'
* l'azzeramento che aspetta di avere
* l'archivio al sicuro prima di partire.
*/
public function __construct(
public string $operationId,
public bool $perAzzeramento = false,
) {
}
public function handle(BackupOperationState $stato, BackupArchive $archivio, XmlExchange $exchange): void
{
$stato->markRunning($this->operationId);
$stato->fase($this->operationId, 'Lettura dei dati gestionali');
$destinazione = self::percorso($this->operationId);
try {
$this->rimuoviArchiviVecchi($archivio);
$xml = $exchange->currentXmlBackup();
$stato->fase($this->operationId, 'Compressione dei file');
$archivio->create(
$xml,
$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);
}
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);
}
}
}
}