++ consolidamento funzionalità Backup
This commit is contained in:
@@ -92,10 +92,12 @@ class BackupArchive
|
||||
'files_count' => $totale,
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
if (! $zip->close()) {
|
||||
try {
|
||||
$this->chiudi($zip);
|
||||
} catch (\Throwable $e) {
|
||||
@unlink($destinazione);
|
||||
|
||||
throw new RuntimeException('Scrittura del file di backup non riuscita.');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ($totale === 0) {
|
||||
@@ -116,7 +118,19 @@ class BackupArchive
|
||||
foreach ($files as $relativePath) {
|
||||
// addFile() legge dal disco durante close(): i PDF non
|
||||
// passano mai interi per la memoria del processo.
|
||||
$zip->addFile($disk->path($relativePath), self::PREFIX_STORAGE . $relativePath);
|
||||
//
|
||||
// Il valore di ritorno si controlla: se il file e' sparito
|
||||
// fra allFiles() e qui — cancellato da un'altra richiesta,
|
||||
// o illeggibile per i permessi — addFile() torna false
|
||||
// senza sollevare, e l'archivio finirebbe con un buco di
|
||||
// cui nessuno si accorge fino al giorno del ripristino.
|
||||
if (! $zip->addFile($disk->path($relativePath), self::PREFIX_STORAGE . $relativePath)) {
|
||||
throw new RuntimeException(
|
||||
'Impossibile aggiungere "' . $relativePath . '" al backup: '
|
||||
. 'il file non e\' leggibile o e\' stato rimosso durante la creazione.'
|
||||
);
|
||||
}
|
||||
|
||||
$nelLotto++;
|
||||
$fatti++;
|
||||
|
||||
@@ -133,11 +147,62 @@ class BackupArchive
|
||||
throw $e;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->verificaArchivio($destinazione, $totale);
|
||||
} catch (\Throwable $e) {
|
||||
@unlink($destinazione);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$onProgress && $onProgress($totale, $totale);
|
||||
|
||||
return $destinazione;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rilegge l'archivio appena scritto e controlla che contenga quello che
|
||||
* dichiara.
|
||||
*
|
||||
* Un backup e' l'unico file di cui non ci si accorge che era rotto finche'
|
||||
* non serve. Costa una riapertura e la lettura della directory centrale —
|
||||
* non dei contenuti — quindi e' un controllo a buon mercato che copre
|
||||
* troncamenti, chiusure a meta' e lotti persi.
|
||||
*/
|
||||
private function verificaArchivio(string $path, int $fileAttesi): void
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
|
||||
if ($zip->open($path, ZipArchive::RDONLY) !== true) {
|
||||
throw new RuntimeException('Il backup appena creato non e\' rileggibile.');
|
||||
}
|
||||
|
||||
$trovati = 0;
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$nome = $zip->getNameIndex($i);
|
||||
if ($nome !== false && str_starts_with($nome, self::PREFIX_STORAGE)) {
|
||||
$trovati++;
|
||||
}
|
||||
}
|
||||
|
||||
$haDati = $zip->locateName(self::ENTRY_DATA) !== false;
|
||||
$haManifest = $zip->locateName(self::ENTRY_MANIFEST) !== false;
|
||||
|
||||
$zip->close();
|
||||
|
||||
if (! $haDati || ! $haManifest) {
|
||||
throw new RuntimeException('Il backup appena creato e\' incompleto: manca data.xml o manifest.json.');
|
||||
}
|
||||
|
||||
if ($trovati !== $fileAttesi) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Il backup appena creato contiene %d file invece di %d: archivio scartato.',
|
||||
$trovati,
|
||||
$fileAttesi
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apre l'archivio in append, esegue il callback e lo chiude, garantendo la
|
||||
* chiusura anche se il callback solleva.
|
||||
@@ -152,14 +217,21 @@ class BackupArchive
|
||||
try {
|
||||
$callback($zip);
|
||||
} catch (\Throwable $e) {
|
||||
$zip->close();
|
||||
// La chiusura di cortesia non deve poter sostituire l'errore vero:
|
||||
// dopo un close() fallito l'oggetto ZipArchive resta inutilizzabile
|
||||
// e ogni metodo successivo solleva "Invalid or uninitialized Zip
|
||||
// object", che finirebbe sotto gli occhi dell'amministratore al
|
||||
// posto del messaggio che spiega davvero cos'e' andato storto.
|
||||
try {
|
||||
$zip->close();
|
||||
} catch (\Throwable) {
|
||||
// gia' chiuso o invalido: non c'e' niente da salvare
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (! $zip->close()) {
|
||||
throw new RuntimeException('Scrittura del file di backup non riuscita.');
|
||||
}
|
||||
$this->chiudi($zip);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,15 +241,42 @@ class BackupArchive
|
||||
*/
|
||||
private function chiudiEriapri(ZipArchive $zip, string $path): void
|
||||
{
|
||||
if (! $zip->close()) {
|
||||
throw new RuntimeException('Scrittura del file di backup non riuscita.');
|
||||
}
|
||||
$this->chiudi($zip);
|
||||
|
||||
if ($zip->open($path) !== true) {
|
||||
throw new RuntimeException('Impossibile riaprire il file di backup.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chiude l'archivio riportando il motivo del fallimento.
|
||||
*
|
||||
* E' in close() che libzip legge davvero i file e scrive l'archivio, quindi
|
||||
* e' qui che si manifestano permessi negati, disco pieno e file spariti. Un
|
||||
* "scrittura non riuscita" secco lascerebbe l'amministratore senza il pezzo
|
||||
* che serve — quale file, e perche' — che sta in getStatusString().
|
||||
*/
|
||||
private function chiudi(ZipArchive $zip): void
|
||||
{
|
||||
$motivo = '';
|
||||
|
||||
try {
|
||||
if ($zip->close()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$motivo = (string) $zip->getStatusString();
|
||||
} catch (\Throwable $e) {
|
||||
$motivo = $e->getMessage();
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
'Scrittura del file di backup non riuscita'
|
||||
. ($motivo !== '' ? ': ' . rtrim($motivo, '. ') . '.' : '.')
|
||||
. ' Cause tipiche: un file non leggibile nel volume storage, o spazio esaurito.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apre il pacchetto, ne verifica la struttura ed estrae i file in una
|
||||
* cartella di staging, restituendo l'XML e il percorso dello staging.
|
||||
@@ -233,9 +332,17 @@ class BackupArchive
|
||||
$totalBytes = 0;
|
||||
$extracted = 0;
|
||||
|
||||
// Le entry di storage/ sono tutte tranne manifest e data.xml: e' il
|
||||
// totale su cui si misura l'estrazione.
|
||||
$daEstrarre = max(0, $zip->numFiles - 2);
|
||||
// Il totale si conta scorrendo i nomi, non come "numFiles meno le
|
||||
// due entry di servizio": un archivio con una entry in piu' (una
|
||||
// cartella dichiarata, un file aggiunto a mano) sfalserebbe la
|
||||
// barra fino a farla superare il 100%.
|
||||
$daEstrarre = 0;
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$nome = $zip->getNameIndex($i);
|
||||
if ($nome !== false && str_starts_with($nome, self::PREFIX_STORAGE) && ! str_ends_with($nome, '/')) {
|
||||
$daEstrarre++;
|
||||
}
|
||||
}
|
||||
$onProgress && $onProgress(0, $daEstrarre);
|
||||
|
||||
for ($index = 0; $index < $zip->numFiles; $index++) {
|
||||
|
||||
Reference in New Issue
Block a user