Un salvataggio che muore a metà strada, che sia per un riavvio forzato, un processo terminato o un disco che si riempie durante la scrittura, ha tradizionalmente significato una cosa per un formato costruito attorno a scritture in loco: qualunque byte abbia raggiunto il disco prima dell'interruzione è ciò che ti ritrovi, e una cartella di lavoro troncata non si riapre più. HotXLS chiude questa modalità di guasto con un percorso di salvataggio sicuro in caso di crash usato per ogni file XLSX, ODS e XLS classico che scrive. Ogni chiamata SaveAs scrive il nuovo file completo in un file temporaneo creato accanto alla destinazione, quindi lo conferma con una singola rinomina atomica MoveFileExW dell'API Windows, cosicché un salvataggio interrotto possa solo non riuscire a produrre il nuovo file, senza mai danneggiare quello che già avevi. La stessa disciplina di preparazione-poi-scambio funziona in modo uniforme su entrambi i motori di salvataggio di HotXLS, lo scrittore BIFF8 dietro l'XLS classico e lo scrittore OOXML dietro XLSX e ODS, ed è uno schema che vale la pena prendere in prestito per qualsiasi file che il tuo stesso codice Delphi sovrascriva direttamente, fogli di calcolo o meno
Cosa succede se un salvataggio di cartella di lavoro viene interrotto a metà strada?
La risposta diretta è che dipende interamente da come lo scrittore tocca il file di destinazione, e l'implementazione comune, aprire il file di destinazione e trasmettere il nuovo contenuto direttamente in esso, va bene finché non va mai storto nulla. Nel momento in cui qualcosa va storto, un crash, una terminazione forzata del processo, una condivisione di rete che cade a metà scrittura, il file su disco viene lasciato in qualunque stato intermedio lo scrittore avesse raggiunto: una directory centrale ZIP mai accodata per XLSX o ODS, oppure uno stream BIFF privo di record che un lettore si aspetta per l'XLS classico. Excel non ripara questo con eleganza, e nemmeno lo fa qualsiasi altro consumatore che si aspetti un file completo, quindi il risultato pratico è una cartella di lavoro che ieri si apriva bene e oggi si rifiuta di aprirsi
Come HotXLS prepara ogni salvataggio dietro un unico scambio atomico
HotXLS non apre mai direttamente il file di destinazione per la scrittura, per nessuno dei tre formati che salva. La sequenza ha sempre la stessa forma: costruire l'output completo da qualche parte che non sia il file che l'utente già ha su disco, e spostarlo in posizione solo una volta che quella costruzione sia pienamente riuscita. In concreto, SaveAs crea un file temporaneo vuoto nella stessa cartella del percorso di destinazione, scrive l'intera nuova cartella di lavoro in quel file temporaneo, e solo dopo che quella scrittura ritorna senza errori conferma il file temporaneo sopra la destinazione con una singola rinomina. Nulla di questo richiede una proprietà da attivare; è semplicemente ciò che SaveAs fa per un normale percorso file, ad ogni chiamata
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Report');
Sheet.Cells[1, 1].Value := 'Nothing special to enable here';
// If this call is interrupted, monthly-report.xlsx on disk stays
// either the old version, complete, or the new version, complete
if Book.SaveAs('monthly-report.xlsx', xlsxOpenXMLWorkbook) <> 1 then
raise Exception.Create('Save failed, see Book.LastDiagnostic');
finally
Book.Free;
end;
end;
La stessa disciplina si applica allo scrittore XLS classico, non solo a quello OOXML, e i due file temporanei condividono persino una convenzione di denominazione: entrambi chiamano l'API Windows GetTempFileNameW con il prefisso hxl, quindi un salvataggio interrotto prima della pulizia può lasciare dietro di sé un file estraneo con un nome come hxl4C2A.tmp accanto alla tua cartella di lavoro. Quel file non è corruzione, è la prova che il meccanismo ha funzionato esattamente come previsto: la scrittura incompleta si è fermata lì, e la tua vera cartella di lavoro non è mai stata aperta per la scrittura in primo luogo. Vederne uno dopo un crash è sicuro da eliminare e non c'è nulla da indagare
Perché preparare il file temporaneo accanto alla cartella di lavoro invece che in %TEMP%?
La risposta breve è che la rinomina di MoveFileExW è atomica solo quando l'origine e la destinazione si trovano sullo stesso volume, e il modo più sicuro per garantirlo senza chiedere al chiamante di configurare nulla è derivare la posizione del file temporaneo dal percorso di destinazione stesso. HotXLS calcola la cartella propria della destinazione e la passa direttamente a GetTempFileNameW, cosicché il file temporaneo venga sempre creato sullo stesso disco, lo stesso volume, del file che sta per sostituire, automaticamente, ad ogni salvataggio. Se la libreria avesse invece preparato le scritture nella cartella temp di sistema, un percorso di destinazione su un disco diverso o un volume di rete mappato trasformerebbe il passaggio finale in un'operazione cross-volume, che l'API Windows rifiuta apertamente oppure, se un chiamante opta esplicitamente con un flag aggiuntivo che HotXLS qui non imposta, degrada silenziosamente in una copia non atomica seguita da un'eliminazione, riaprendo esattamente la finestra di interruzione che l'intero meccanismo esiste per chiudere
Il passaggio di commit: MoveFileExW, write-through, e cosa succede in caso di fallimento
Il passaggio finale di ogni salvataggio è esattamente una chiamata API Windows, MoveFileExW, che porta due flag, ciascuno con un compito distinto. MOVEFILE_REPLACE_EXISTING è ciò che permette alla rinomina di atterrare su un file già esistente; senza di esso, una rinomina che punta a un percorso esistente fallisce semplicemente, il che vanificherebbe l'intero scopo di un salvataggio pensato per sostituire una cartella di lavoro che già hai. MOVEFILE_WRITE_THROUGH copre la durabilità: dice alla funzione di non ritornare finché lo spostamento non sia effettivamente completato su disco, invece di ritornare non appena la rinomina viene semplicemente accodata, chiudendo una finestra di corsa critica più stretta ma reale in cui un crash immediatamente dopo il ritorno di SaveAs potrebbe comunque cogliere lo scambio in corso. Se il file temporaneo non può essere creato, o la rinomina finale fallisce per qualsiasi motivo (un problema di permessi, una destinazione bloccata, un disallineamento di volume), HotXLS elimina da sé il file temporaneo invece di lasciare rifiuti in giro, e il file di destinazione resta esattamente come era prima della chiamata
Result := Book.SaveAs(TargetPath, xlsxOpenXMLWorkbook);
if Result <> 1 then
begin
// TargetPath on disk is unchanged; safe to retry, alert, or
// fall back to a different path without touching prior output
LogWriter.Write(Format('SaveAs failed (%d): %s',
[Book.LastDiagnostic.Code, Book.LastDiagnostic.Message]));
Exit(False);
end;
SaveAs stessa mantiene la convenzione di ritorno condivisa in tutto HotXLS, uno in caso di successo, un numero negativo in caso di fallimento, ma un semplice intero non dice perché un salvataggio sia fallito, e trattare ogni risultato negativo allo stesso modo butta via informazioni che una politica di retry potrebbe realmente usare. La proprietà LastDiagnostic, e la più completa collezione Diagnostics dietro di essa, porta il messaggio che HotXLS ha generato internamente, distinguendo un file temporaneo che non è potuto essere creato da una rinomina che Windows ha rifiutato. Un job batch che registra Code e Message a ogni SaveAs fallito costruisce esattamente le prove che vuoi avere la volta in cui un cliente segnala un salvataggio che silenziosamente non ha fatto nulla
L'XLS classico paga in memoria, XLSX e ODS pagano in disco
I due motori di salvataggio raggiungono lo stesso esito sicuro in caso di crash per strade diverse, e la differenza conta se stai già ottimizzando l'uno o l'altro per un job batch di grandi dimensioni. Lo scrittore XLS classico costruisce prima l'intero documento composto OLE in memoria, usando storage strutturato sostenuto da un handle di memoria, e copia solo quel buffer finito nel file temporaneo gemello in un'unica scrittura; il ragionamento nel codice sorgente proprio di HotXLS è diretto: costruire prima l'intero file in memoria è ciò che impedisce a un salvataggio fallito o annullato di troncare mai la destinazione. Lo scrittore XLSX e ODS invece trasmette le proprie voci ZIP nel file temporaneo man mano che vengono prodotte, la stessa preparazione a livello di file con un profilo di memoria diverso. Se ti stai già appoggiando a StreamingWrite per mantenere le esportazioni XLSX di grandi dimensioni entro il limite di memoria di un contenitore, sappi che la leva equivalente per l'esportazione XLS classica non esiste nella stessa forma: la garanzia sicura in caso di crash è comunque incondizionata, ma un'esportazione .xls legacy molto grande mantiene comunque il proprio output completo in RAM, un compromesso trattato più in profondità nel nostro articolo sulle scritture in streaming per i job batch su server
Applicare lo stesso schema al di fuori di HotXLS, e dove finisce la garanzia
Prendere in prestito lo schema è per lo più una questione di collegare le stesse due chiamate API Windows su cui HotXLS fa affidamento internamente. GetTempFileNameW ti fornisce un file vuoto con nome univoco in una cartella a tua scelta, e MoveFileExW conferma la tua scrittura completata sopra la vera destinazione in un unico passaggio; una versione minimale della stessa routine che HotXLS esegue prima di ogni SaveAs si presenta così
function SaveFileAtomically(const Path: WideString; const Contents: TBytes): Boolean;
var
Dir, TempName: WideString;
Buffer: array[0..MAX_PATH] of WideChar;
FS: TFileStream;
begin
Result := False;
Dir := ExtractFilePath(ExpandFileName(Path));
FillChar(Buffer, SizeOf(Buffer), 0);
if GetTempFileNameW(PWideChar(Dir), 'app', 0, @Buffer[0]) = 0 then
Exit;
TempName := PWideChar(@Buffer[0]);
try
FS := TFileStream.Create(TempName, fmCreate or fmShareExclusive);
try
FS.WriteBuffer(Contents[0], Length(Contents));
finally
FS.Free;
end;
Result := MoveFileExW(PWideChar(TempName), PWideChar(ExpandFileName(Path)),
MOVEFILE_REPLACE_EXISTING or MOVEFILE_WRITE_THROUGH);
finally
if not Result then
DeleteFileW(PWideChar(TempName));
end;
end;
La garanzia ha confini reali che vale la pena conoscere prima di affidarvisi ciecamente. Preparare una copia completa prima di sostituire l'originale significa che un salvataggio ha temporaneamente bisogno di spazio su disco sia per il vecchio file sia per il nuovo, all'incirca il doppio della dimensione della cartella di lavoro per la durata della scrittura, il che va bene per un report e merita una verifica per un'esportazione multi-gigabyte eseguita contro un volume quasi pieno. Il file temporaneo deve anche atterrare nella stessa cartella della destinazione, quindi qualunque account sotto cui HotXLS sta girando ha bisogno del permesso di creazione file su quella cartella specificamente, non semplicemente del permesso di sovrascrivere il singolo file che già conosce; un deployment che blocca una cartella di destinazione a modifiche in loco di nomi file esistenti specifici, anziché accesso in scrittura a livello di cartella, vedrà SaveAs fallire al passaggio del file temporaneo anche se la scrittura diretta equivalente sarebbe riuscita
Altri due confini vale la pena segnalare chiaramente. Una destinazione su una condivisione di rete o dentro una cartella sincronizzata da OneDrive o un client simile può comportarsi diversamente da NTFS locale anche se Windows continua a segnalarla come un singolo volume, poiché il driver del filesystem di fronte ad essa potrebbe non implementare la rinomina nello stesso modo; se il tuo target di deployment salva su un percorso di rete, vale la pena testare specificamente un'interruzione forzata lì invece di presupporre che il comportamento del disco locale si trasferisca. E l'intero meccanismo è limitato al salvataggio in un file con nome. Chiama invece SaveAs contro un TStream, e HotXLS scrive direttamente in qualunque stream tu gli abbia passato, senza alcun file di destinazione da preparare o proteggere, perché la durabilità di quello stream (un buffer di memoria, un upload di rete, un blob di database) è interamente responsabilità del tuo codice da quel punto in poi
Un passaggio di verifica può poi fare affidamento esattamente su questa garanzia, incluso il tipo integrato in un banco di lavoro di audit e conversione delle cartelle di lavoro: un file riaperto che torna incompleto o mancante è un vero problema di conversione da rincorrere, mai un salvataggio interrotto a metà che ha lasciato qualcosa di ambiguo su disco. Le scritture intermedie sicure in caso di crash sono integrate in SaveAs per ogni cartella di lavoro XLSX, ODS e XLS classica prodotta dal componente HotXLS per Delphi e C++Builder, senza alcuna configurazione richiesta per attivarla