Uloženie, ktoré zomrie v polovici, či už kvôli vynútenému reštartu, ukončenému procesu, alebo disku, ktorý sa zaplní uprostred zápisu, tradične znamenalo jedno pre formát postavený na zápise na mieste: akékoľvek bajty, ktoré sa dostali na disk pred prerušením, sú to, čo dostanete späť, a orezaný zošit sa už znova neotvorí. HotXLS tento druh zlyhania uzatvára cestou ukladania odolnou voči pádu, ktorá sa používa pre každý súbor XLSX, ODS a klasický XLS, aký zapisuje. Každé volanie SaveAs zapíše kompletný nový súbor do dočasného súboru vytvoreného vedľa cieľa, a potom ho potvrdí jediným atomickým premenovaním MoveFileExW z API Windows, takže prerušené uloženie môže nanajvýš zlyhať pri vytvorení nového súboru, no nikdy nepoškodí ten, ktorý ste už mali. Rovnaká disciplína etapa-a-potom-výmena beží rovnomerne cez oba ukladacie enginy HotXLS, zapisovač BIFF8 za klasickým XLS aj zapisovač OOXML za XLSX a ODS, a je to vzor, ktorý sa oplatí prevziať pre akýkoľvek súbor, ktorý váš vlastný kód v Delphi priamo prepisuje, tabuľky alebo nie
Čo sa stane, ak sa uloženie zošita preruší uprostred?
Priama odpoveď je, že to úplne závisí od toho, ako sa zapisovač dotýka cieľového súboru, a bežná implementácia, otvorenie cieľového súboru a streamovanie nového obsahu priamo doňho, je v poriadku, pokým sa nič nepokazí. Vo chvíli, keď sa niečo pokazí, pád, vynútené ukončenie procesu, sieťový disk, ktorý odpadne uprostred zápisu, súbor na disku zostane v akomkoľvek medzistave, ktorý zapisovač dosiahol: centrálny adresár ZIP, ktorý sa nikdy nepripojil pre XLSX alebo ODS, alebo prúd BIFF chýbajúci záznamy, ktoré čítačka očakáva pre klasický XLS. Excel to elegantne neopraví, a nerobí to ani žiadny iný konzument očakávajúci kompletný súbor, takže praktickým výsledkom je zošit, ktorý sa včera otvoril v poriadku a dnes sa otvoriť odmieta
Ako HotXLS stagingom pripraví každé uloženie za jednu atomickú výmenu
HotXLS nikdy neotvára cieľový súbor priamo na zápis, pre žiadny z troch formátov, aké ukladá. Postupnosť má rovnaký tvar zakaždým: zostaviť kompletný výstup niekde, čo nie je súbor, ktorý už používateľ má na disku, a presunúť ho na miesto až vtedy, keď táto stavba plne uspeje. Konkrétne, SaveAs vytvorí prázdny dočasný súbor v tom istom priečinku ako cieľová cesta, zapíše celý nový zošit do tohto dočasného súboru, a až potom, čo sa tento zápis vráti bez chyby, potvrdí dočasný súbor nad cieľom jediným premenovaním. Nič z toho nevyžaduje vlastnosť na zapnutie; je to jednoducho to, čo SaveAs robí pre obyčajnú cestu k súboru, pri každom volaní
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;
Rovnaká disciplína platí aj pre klasický zapisovač XLS, nielen pre OOXML, a oba dočasné súbory dokonca zdieľajú konvenciu pomenovania: oba volajú API Windows GetTempFileNameW s prefixom hxl, takže uloženie prerušené ešte pred vyčistením môže za sebou zanechať zablúdený súbor s názvom podobným hxl4C2A.tmp sediaci vedľa vášho zošita. Tento súbor nie je poškodenie, je to dôkaz, že mechanizmus fungoval presne tak, ako bol navrhnutý: nekompletný zápis sa tam zastavil, a váš skutočný zošit nebol na zápis vôbec otvorený. Uvidieť taký súbor po páde je bezpečné vymazať a nie je čo vyšetrovať
Prečo staging dočasného súboru vedľa zošita namiesto v %TEMP%?
Krátka odpoveď je, že premenovanie cez MoveFileExW je atomické iba vtedy, keď zdroj a cieľ sedia na tom istom zväzku, a najistejší spôsob, ako to zaručiť bez toho, aby ste žiadali volajúceho čokoľvek nastaviť, je odvodiť polohu dočasného súboru priamo z cieľovej cesty. HotXLS vypočíta vlastný priečinok cieľa a tento adresár odovzdá priamo GetTempFileNameW, takže dočasný súbor sa vždy vytvorí na tom istom disku, tom istom zväzku, ako súbor, ktorý má nahradiť, automaticky, pri každom uložení. Keby knižnica namiesto toho stagovala zápisy v systémovom priečinku temp, cieľová cesta na inom disku alebo pripojenom sieťovom zväzku by zmenila záverečný krok na operáciu naprieč zväzkami, ktorú API Windows buď rovno odmietne, alebo, ak sa volajúci výslovne prihlási extra príznakom, ktorý tu HotXLS nenastavuje, ticho degraduje na neatomickú kombináciu kopírovania a mazania, čím sa znova otvorí presne to okno prerušenia, kvôli ktorému tento celý mechanizmus existuje
Krok potvrdenia: MoveFileExW, zápis naskrz a čo sa stane pri zlyhaní
Posledný krok každého uloženia je presne jedno volanie API Windows, MoveFileExW, nesúce dva príznaky, ktoré každý robí odlišnú prácu. MOVEFILE_REPLACE_EXISTING je to, čo dovoľuje premenovaniu skončiť na súbore, ktorý už existuje; bez neho premenovanie mieriace na existujúcu cestu jednoducho zlyhá, čo by porazilo celý zmysel uloženia určeného na nahradenie zošita, ktorý už máte. MOVEFILE_WRITE_THROUGH pokrýva trvácnosť: hovorí funkcii, aby sa nevrátila, kým sa presun skutočne nedokončí na disku, namiesto toho, aby sa vrátila hneď, ako je premenovanie iba zaradené do fronty, čím sa uzatvára užší, ale skutočný pretek, kde pád tesne po tom, čo sa SaveAs vráti, by mohol stále zachytiť výmenu vo vzduchu. Ak sa dočasný súbor nedá vytvoriť, alebo záverečné premenovanie z akéhokoľvek dôvodu zlyhá (problém s oprávnením, uzamknutý cieľ, nesúlad zväzkov), HotXLS sám vymaže dočasný súbor namiesto toho, aby zanechal odpad, a cieľový súbor zostane presne taký, aký bol pred volaním
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;
Samotné SaveAs drží konvenciu návratovej hodnoty zdieľanú naprieč HotXLS, jednotku pri úspechu, záporné číslo pri zlyhaní, no holé celé číslo nehovorí, prečo uloženie zlyhalo, a zaobchádzať s každým záporným výsledkom rovnako zahadzuje informáciu, ktorú by politika opakovania mohla skutočne využiť. Vlastnosť LastDiagnostic, a plnšia kolekcia Diagnostics za ňou, nesie správu, ktorú HotXLS interne vygeneroval, rozlišujúc dočasný súbor, ktorý sa nepodarilo vytvoriť, od premenovania, ktoré Windows odmietol. Dávková úloha, ktorá pri každom zlyhanom SaveAs zaloguje Code a Message, si vybuduje presne ten dôkaz, aký chcete mať v ten jeden raz, keď zákazník ohlási uloženie, ktoré ticho neurobilo nič
Klasický XLS platí pamäťou, XLSX a ODS platia diskom
Oba ukladacie enginy dosahujú rovnaký výsledok odolný voči pádu odlišnými cestami, a tento rozdiel je dôležitý, ak už dolaďujete niektorý z nich pre veľkú dávkovú úlohu. Zapisovač klasického XLS najprv postaví celý zložený dokument OLE v pamäti, pomocou štruktúrovaného úložiska podloženého handlom pamäte, a až potom skopíruje tento hotový buffer do sesterského dočasného súboru jedným zápisom; úvaha vo vlastnom zdrojovom kóde HotXLS je priama: postaviť celý súbor najprv v pamäti je to, čo bráni zlyhanému alebo zrušenému uloženiu, aby niekedy orezal cieľ. Zapisovač XLSX a ODS namiesto toho streamuje svoje položky ZIP do dočasného súboru tak, ako sa produkujú, rovnaký staging na úrovni súboru s odlišným pamäťovým profilom. Ak sa už spoliehate na StreamingWrite, aby ste udržali veľké exporty XLSX vnútri pamäťových limitov kontajnera, vedzte, že ekvivalentná páka pre export klasického XLS v tej istej forme neexistuje: záruka odolnosti voči pádu je bezpodmienečná v oboch prípadoch, no veľmi veľký export do staršieho .xls drží svoj kompletný výstup v RAM bez ohľadu na to, kompromis, ktorý do hĺbky pokrýva náš článok o streamovanom zápise pre dávkové úlohy na serveri
Aplikovanie rovnakého vzoru mimo HotXLS a kde záruka končí
Prevzatie tohto vzoru je väčšinou otázkou zapojenia tých istých dvoch volaní API Windows, na ktoré sa HotXLS interne spolieha. GetTempFileNameW vám odovzdá jedinečne pomenovaný, prázdny súbor v priečinku podľa vášho výberu, a MoveFileExW potvrdí váš hotový zápis nad skutočným cieľom v jednom kroku; minimálna verzia tej istej rutiny, ktorú HotXLS spúšťa pred každým SaveAs, vyzerá takto
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;
Táto záruka má skutočné hrany, ktoré sa oplatí poznať skôr, než sa na ňu spoľahnete naslepo. Staging plnej kópie pred nahradením originálu znamená, že uloženie krátkodobo potrebuje diskový priestor pre starý aj nový súbor naraz, zhruba dvojnásobok veľkosti zošita na dobu zápisu, čo je v poriadku pre report a stojí za kontrolu pri exporte veľkosti niekoľkých gigabajtov bežiacom voči takmer plnému zväzku. Dočasný súbor sa navyše musí ocitnúť v tom istom priečinku ako cieľ, takže akýkoľvek účet, pod ktorým HotXLS beží, potrebuje oprávnenie na vytváranie súborov práve v tomto priečinku, nielen oprávnenie na prepísanie toho jedného súboru, o ktorom už vie; nasadenie, ktoré uzamkne cieľový priečinok iba na úpravy na mieste konkrétnych existujúcich názvov súborov, namiesto prístupu na zápis na úrovni priečinka, uvidí zlyhanie SaveAs na kroku dočasného súboru, aj keby ekvivalentný priamy zápis uspel
Ešte dve hranice sa oplatí jasne pomenovať. Cieľ na sieťovom disku alebo vnútri priečinka synchronizovaného cez OneDrive alebo podobného klienta sa môže správať odlišne od lokálneho NTFS, aj keď ho Windows stále hlási ako jediný zväzok, keďže ovládač súborového systému pred ním nemusí implementovať premenovanie rovnakým spôsobom; ak vaše nasadenie ukladá cez sieťovú cestu, oplatí sa otestovať vynútené prerušenie tam špecificky namiesto predpokladu, že sa správanie lokálneho disku prenesie ďalej. A celý mechanizmus je obmedzený na ukladanie do pomenovaného súboru. Zavolajte SaveAs voči TStream namiesto toho a HotXLS zapisuje priamo do akéhokoľvek prúdu, ktorý ste mu odovzdali, bez akéhokoľvek cieľového súboru na staging alebo ochranu, pretože trvácnosť tohto prúdu (pamäťový buffer, sieťový upload, databázový blob) je od toho bodu úplne zodpovednosťou vášho kódu
Overovací prechod sa potom môže spoľahnúť presne na túto záruku, vrátane toho vstavaného v pracovnej stanici na audit a konverziu zošitov: znova otvorený súbor, ktorý sa vráti skrátený alebo chýbajúci, je skutočný konverzný problém na vystopovanie, nikdy uloženie, ktoré sa prerušilo v polovici a zanechalo na disku niečo nejednoznačné. Ukladanie v etapách odolné voči pádu je zabudované do SaveAs pre každý zošit XLSX, ODS a klasický XLS produkovaný komponentom HotXLS pre Delphi a C++Builder, bez akejkoľvek konfigurácie potrebnej na jeho zapnutie