Egy mentés, amely félúton meghal, akár egy kényszerített újraindítás, egy megölt folyamat, vagy egy lemez miatt, amely íráskor megtelik, hagyományosan egy dolgot jelentett egy olyan formátum esetén, amely helyben történő írásra épül: bármilyen bájt, amely az megszakítás előtt elérte a lemezt, azt kapod vissza, és egy csonka munkafüzet nem nyílik meg újra. A HotXLS egy összeomlásbiztos mentési útvonallal zárja le ezt a hibamódot, amelyet minden XLSX-, ODS-, és klasszikus XLS-fájlnál használ, amit ír. Minden SaveAs hívás megírja a teljes új fájlt egy, a célhely mellé létrehozott ideiglenes fájlba, majd egyetlen atomi MoveFileExW átnevezéssel véglegesíti azt a Windows API-ból, így egy megszakított mentés csak abban tud elbukni, hogy nem hozza létre az új fájlt, soha nem károsítja azt, ami már megvolt. Ugyanaz a staged-majd-cserél fegyelem fut egységesen a HotXLS mindkét mentőmotorján keresztül, a klasszikus XLS mögötti BIFF8-írón, és az XLSX és ODS mögötti OOXML-írón, és ez egy minta, amit érdemes kölcsönvenni bármilyen fájlhoz, amit a saját Delphi-kódod közvetlenül felülír, legyen az táblázat vagy sem
Mi történik, ha egy munkafüzet mentése félúton megszakad?
A közvetlen válasz az, hogy teljesen attól függ, hogyan érinti az író a célfájlt, és a gyakori megvalósítás, a célfájl megnyitása és az új tartalom közvetlen streamelése bele, addig rendben van, amíg semmi nem megy rosszul. Abban a pillanatban, amikor valami elromlik, egy összeomlás, egy kényszerített folyamatleállítás, egy hálózati megosztás, amely íráskor leesik, a lemezen lévő fájl abban a köztes állapotban marad, amit az író elért: egy ZIP központi könyvtár, amely soha nem fűződött hozzá az XLSX-hez vagy ODS-hez, vagy egy BIFF-folyam, amelyből hiányoznak azok a rekordok, amiket egy olvasó vár klasszikus XLS esetén. Az Excel ezt nem javítja ki kecsesen, és semmilyen más fogyasztó sem, amely teljes fájlt vár, így a gyakorlati eredmény egy munkafüzet, amely tegnap még megnyílt rendben, és ma megtagadja a megnyitást
Hogyan staged a HotXLS minden mentést egyetlen atomi csere mögé
A HotXLS soha nem nyitja meg közvetlenül írásra a célfájlt, a három formátum egyikéhez sem, amit ment. A sorrend minden alkalommal ugyanaz az alak: építsd fel a teljes kimenetet valahol, ami nem az a fájl, ami már megvan a felhasználónak a lemezen, és csak akkor mozgasd a helyére, amint az az építés teljesen sikeresen befejeződött. Konkrétan a SaveAs létrehoz egy üres ideiglenes fájlt ugyanabban a mappában, mint a célútvonal, megírja a teljes új munkafüzetet ebbe az ideiglenes fájlba, és csak azután, hogy ez az írás hiba nélkül visszatér, véglegesíti az ideiglenes fájlt a célhely fölé egyetlen átnevezéssel. Ehhez semmi nem igényel bekapcsolandó tulajdonságot; egyszerűen ez az, amit a SaveAs csinál egy egyszerű fájlútvonalhoz, minden hívásnál
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;
Ugyanez a fegyelem érvényes a klasszikus XLS-íróra is, nem csak az OOXML-re, és a két ideiglenes fájl még egy elnevezési konvenciót is megoszt: mindkettő meghívja a Windows GetTempFileNameW API-ját a hxl előtaggal, így egy takarítás előtt megszakított mentés hátrahagyhat egy elszórt fájlt egy olyan névvel, mint a hxl4C2A.tmp, a munkafüzeted mellett ülve. Az a fájl nem sérülés, hanem bizonyíték arra, hogy a mechanizmus pontosan úgy működött, ahogyan tervezve volt: a befejezetlen írás ott állt meg, és a tényleges munkafüzeted soha nem is nyílt meg írásra eleve. Ha egy összeomlás után látsz egyet, biztonságosan törölhető, és nincs semmi kivizsgálnivaló
Miért staged az ideiglenes fájlt a munkafüzet mellé a %TEMP% helyett?
A rövid válasz az, hogy a MoveFileExW átnevezése csak akkor atomi, ha a forrás és a célhely ugyanazon a köteten ül, és a legbiztosabb mód ennek garantálására anélkül, hogy a hívónak bármit is konfigurálnia kellene, az, hogy az ideiglenes fájl helyét magából a célútvonalból származtatjuk. A HotXLS kiszámítja a cél saját mappáját, és azt a könyvtárat egyenesen a GetTempFileNameW-nek adja, így az ideiglenes fájl mindig ugyanazon a meghajtón, ugyanazon a köteten jön létre, mint az a fájl, amit épp le fog cserélni, automatikusan, minden mentésnél. Ha a könyvtár ehelyett a rendszer temp mappájában stagelte volna az írásokat, egy másik meghajtón, vagy egy csatolt hálózati köteten lévő célútvonal a végső lépést kötetek közötti művelettel tenné, amit a Windows API vagy egyszerűen elutasít, vagy, ha egy hívó kifejezetten bekapcsolja egy extra jelzővel, amit a HotXLS itt nem állít be, csendben egy nem-atomi másolás plusz törlés műveletre degradál, pontosan azt a megszakítási ablakot újranyitva, amelynek lezárására ez az egész mechanizmus létezik
A véglegesítési lépés: MoveFileExW, write-through, és mi történik bukás esetén
Minden mentés végső lépése pontosan egy Windows API-hívás, a MoveFileExW, amely két jelzőt hordoz, amelyek mindegyike külön munkát végez. A MOVEFILE_REPLACE_EXISTING az, ami engedélyezi, hogy az átnevezés egy már létező fájlon landoljon; ez nélkül egy átnevezés, amely egy már létező útvonalat céloz meg, egyszerűen elbukik, ami meghiúsítaná az egész pontját egy olyan mentésnek, amelynek le kellene cserélnie egy munkafüzetet, ami már megvan. A MOVEFILE_WRITE_THROUGH a tartósságot fedi le: azt mondja a függvénynek, hogy ne térjen vissza, amíg a mozgatás ténylegesen be nem fejeződött a lemezen, ahelyett hogy visszatérne, amint az átnevezés csupán sorba van állítva, lezárva egy szűkebb, de valós versenyhelyzetet, ahol egy, közvetlenül a SaveAs visszatérése utáni összeomlás még mindig elkaphatná a cserét folyamatban. Ha az ideiglenes fájl nem hozható létre, vagy a végső átnevezés bármilyen okból elbukik (egy jogosultsági probléma, egy zárolt célhely, egy kötet-eltérés), a HotXLS maga törli az ideiglenes fájlt, ahelyett hogy szemetet hagyna hátra, és a célfájl pontosan úgy marad, ahogyan a hívás előtt volt
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;
A SaveAs maga megtartja a HotXLS-en keresztül közösen használt visszatérési konvenciót, egy sikeres esetben, egy negatív számot bukás esetén, de egy puszta egész szám nem mondja meg, miért bukott el egy mentés, és minden negatív eredmény ugyanúgy kezelése eldobja azt az információt, amit egy újrapróbálkozási szabályzat ténylegesen felhasználhatna. A LastDiagnostic tulajdonság, és a mögötte lévő teljesebb Diagnostics gyűjtemény, hordozza az üzenetet, amit a HotXLS belsőleg generált, megkülönböztetve egy ideiglenes fájlt, amit nem lehetett létrehozni, egy átnevezéstől, amit a Windows elutasított. Egy kötegelt feladat, amely naplózza a Code-ot és a Message-t minden elbukott SaveAs-nál, pontosan azt a bizonyítékot építi fel, amit szeretnél, amikor egy ügyfél egyszer olyan mentést jelent, amely csendben semmit nem csinált
A klasszikus XLS memóriával fizet, az XLSX és ODS lemezzel
A két mentőmotor különböző útvonalakon jut el ugyanahhoz az összeomlásbiztos eredményhez, és a különbség számít, ha máris hangolod bármelyiket egy nagy kötegelt feladathoz. A klasszikus XLS-író előbb a teljes OLE compound dokumentumot építi fel memóriában, egy memóriahandle-lel alátámasztott strukturált tárolást használva, és csak azt a kész puffert másolja ki a testvér ideiglenes fájlba egyetlen írásban; a HotXLS saját forrásában lévő indoklás közvetlen: a teljes fájl előzetes memóriabeli felépítése az, ami megakadályozza, hogy egy elbukott vagy megszakított mentés valaha is csonkítsa a célfájlt. Az XLSX- és ODS-író ehelyett streameli a ZIP-bejegyzéseit az ideiglenes fájlba, ahogy azok előállnak, ugyanaz a fájlszintű staging, más memóriaprofillal. Ha máris a StreamingWrite-ra támaszkodsz, hogy egy nagy XLSX-export egy konténer memóriakorlátain belül maradjon, tudd, hogy az egyenértékű kar klasszikus XLS-exporthoz nem létezik ugyanabban a formában: az összeomlásbiztos garancia mindkét esetben feltétel nélküli, de egy nagyon nagy örökölt .xls export mindenképp a teljes kimenetét RAM-ban tartja, egy kompromisszum, amelyet mélyebben tárgyal a szerver kötegelt feladatokhoz szóló streamelt írásokról szóló cikkünk
Ugyanennek a mintának az alkalmazása a HotXLS-en kívül, és hol ér véget a garancia
A minta kölcsönvétele leginkább ugyanannak a két Windows API-hívásnak a bekötéséről szól, amelyekre a HotXLS belsőleg támaszkodik. A GetTempFileNameW ad neked egy egyedi nevű, üres fájlt egy általad választott mappában, és a MoveFileExW véglegesíti a befejezett írásodat a valódi célhely fölé egyetlen lépésben; a HotXLS által minden SaveAs előtt futtatott rutin minimális változata így néz ki
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;
A garanciának valódi szélei vannak, amelyeket érdemes ismerni, mielőtt vakon rá támaszkodsz. Egy teljes másolat stagingja az eredeti lecserélése előtt azt jelenti, hogy egy mentésnek röviden lemezterületre van szüksége mind a régi, mind az új fájlhoz, nagyjából a munkafüzet méretének kétszeresére az írás időtartamára, ami rendben van egy jelentéshez, és érdemes ellenőrizni egy több gigabájtos exportnál, amely egy majdnem tele kötet ellen fut. Az ideiglenes fájlnak ugyanabba a mappába is kell landolnia, mint a célhelynek, így bármelyik fiók alatt is fut a HotXLS, kifejezetten fájl-létrehozási jogosultságra van szüksége abban a mappában, nem csupán arra a jogosultságra, hogy felülírja az egy fájlt, amiről már tud; egy telepítés, amely egy célmappát meglévő fájlnevek helyben történő szerkesztésére zár le, mappaszintű írási hozzáférés helyett, azt fogja tapasztalni, hogy a SaveAs elbukik az ideiglenes fájl lépésénél, még akkor is, ha az egyenértékű közvetlen írás sikerült volna
Két további határt érdemes egyértelműen megjelölni. Egy célhely egy hálózati megosztáson, vagy egy OneDrive vagy hasonló kliens által szinkronizált mappán belül másképp viselkedhet, mint a helyi NTFS, még akkor is, ha a Windows egyetlen kötetként jelenti, mivel az előtte lévő fájlrendszer-illesztőprogram esetleg nem ugyanúgy valósítja meg az átnevezést; ha a telepítési célod egy hálózati útvonalon keresztül ment, érdemes ott kifejezetten tesztelni egy kényszerített megszakítást, ahelyett hogy feltételeznéd, hogy a helyi lemez viselkedése átvihető. És az egész mechanizmus egy elnevezett fájlba történő mentésre korlátozódik. Hívd meg a SaveAs-t egy TStream ellen ehelyett, és a HotXLS közvetlenül abba a streambe ír, amit átadtál neki, semmilyen célfájl nélkül, amit staged-elhetne vagy védhetne, mert annak a streamnek (egy memóriapuffer, egy hálózati feltöltés, egy adatbázis-blob) a tartóssága teljes egészében a te kódod felelőssége ettől a ponttól kezdve
Egy verifikációs átfutás pontosan erre a garanciára támaszkodhat utólag, beleértve az olyat is, amely egy munkafüzet-auditáló és -konvertáló munkapadba van beépítve: egy újra megnyitott fájl, amely hiányosan vagy hiányzóan tér vissza, valódi konverziós probléma, amit ki kell vizsgálni, soha nem egy mentés, amely félúton megszakadt, és valami kétértelműt hagyott a lemezen. Az összeomlásbiztos, staged írások be vannak építve a SaveAs-ba minden XLSX-, ODS-, és klasszikus XLS-munkafüzethez, amit a Delphihez és C++Builderhez készült HotXLS Komponens előállít, konfiguráció nélkül a bekapcsolásához