Shranjevanje, ki se prekine na polovici zaradi prisilnega ponovnega zagona, ukinjenega procesa ali diska, ki se med pisanjem zapolni, je pri formatu, zasnovanem za pisanje na mestu, tradicionalno pomenilo eno: vrnjeni dobite bajte, ki so prišli na disk pred prekinitvijo, okrnjen delovni zvezek pa se ne odpre več. HotXLS ta način odpovedi odpravi z varnim postopkom shranjevanja, ki ga uporablja za vsako datoteko XLSX, ODS in klasično XLS, ki jo zapiše. Vsak klic SaveAs zapiše celotno novo datoteko v začasno datoteko, ustvarjeno poleg cilja, nato pa jo potrdi z eno atomsko operacijo MoveFileExW Windows API, zato lahko prekinjeno shranjevanje povzroči le, da nova datoteka ne nastane, nikoli pa ne poškoduje datoteke, ki ste jo že imeli. Enaka disciplina priprave in zamenjave enotno deluje v obeh HotXLS mehanizmih shranjevanja, zapisovalniku BIFF8 za klasični XLS in zapisovalniku OOXML za XLSX in ODS, ta vzorec pa je uporaben za vsako datoteko, ki jo vaša koda Delphi neposredno prepisuje, ne glede na to, ali gre za preglednice
Kaj se zgodi, če se shranjevanje delovnega zvezka prekine na polovici?
Neposreden odgovor je, da je vse odvisno od tega, kako zapisovalnik dostopa do ciljne datoteke, pri čemer je običajna izvedba, ki odpre ciljno datoteko in vanjo neposredno pretaka novo vsebino, varna, dokler se nič ne zalomi. Ko se zgodi sesutje, prisilna ukinitev procesa ali izpad omrežne skupne rabe med pisanjem, datoteka na disku ostane v vmesnem stanju, do katerega je zapisovalnik prišel: pri XLSX ali ODS manjka pripet osrednji imenik ZIP, pri klasičnem XLS pa manjkajo zapisi v toku BIFF, ki jih bralnik pričakuje. Excel tega ne popravi zanesljivo in tudi noben drug uporabnik, ki pričakuje popolno datoteko, zato je praktični rezultat delovni zvezek, ki se je včeraj odprl, danes pa se noče več
Kako HotXLS vsako shranjevanje pripravi za eno atomsko zamenjavo
HotXLS pri nobenem od treh formatov, ki jih shranjuje, ciljne datoteke nikoli ne odpre neposredno za pisanje. Zaporedje je vedno enako: celoten izhod zgradite na mestu, ki ni datoteka, ki jo uporabnik že ima na disku, in ga šele po popolnoma uspešni izdelavi premaknete na pravo mesto. Natančneje, SaveAs ustvari prazno začasno datoteko v isti mapi kot ciljna pot, vanjo zapiše celoten nov delovni zvezek in šele, ko se pisanje vrne brez napake, z eno samo preimenovalno operacijo potrdi začasno datoteko čez cilj. Za to ni treba vključiti nobene lastnosti; tako SaveAs preprosto deluje pri navadni poti datoteke in pri vsakem klicu
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;
Enaka disciplina velja tudi za zapisovalnik klasičnega XLS, ne le za OOXML, obe začasni datoteki pa uporabljata enako poimenovanje: obe pokličeta Windows API GetTempFileNameW s predpono hxl, zato lahko prekinjeno shranjevanje pred čiščenjem pusti osirotelo datoteko z imenom, kot je hxl4C2A.tmp, poleg vašega delovnega zvezka. Ta datoteka ni poškodba, temveč dokaz, da je mehanizem deloval po načrtu: nepopolno pisanje se je ustavilo tam, vaš dejanski delovni zvezek pa se sploh ni odprl za pisanje. Če jo po sesutju opazite, jo lahko varno izbrišete in ni razloga za preiskovanje
Zakaj začasno datoteko pripraviti poleg delovnega zvezka in ne v %TEMP%?
Kratek odgovor je, da je preimenovanje MoveFileExW atomsko samo, ko sta izvor in cilj na istem nosilcu, najzanesljivejši način za to jamstvo brez dodatnega nastavljanja klicatelja pa je izpeljava mesta začasne datoteke iz same ciljne poti. HotXLS izračuna mapo cilja in jo neposredno preda funkciji GetTempFileNameW, zato se začasna datoteka pri vsakem shranjevanju samodejno ustvari na istem pogonu in istem nosilcu kot datoteka, ki jo bo zamenjala. Če bi knjižnica pisanje namesto tega pripravljala v sistemski začasni mapi, bi ciljna pot na drugem pogonu ali preslikanem omrežnem nosilcu zadnji korak spremenila v operacijo med nosilci, ki jo Windows API bodisi takoj zavrne bodisi, če klicatelj izrecno vključi dodatno zastavico, ki je HotXLS tukaj ne nastavi, tiho spremeni v neatomsko kopiranje in brisanje ter znova odpre prav okno za prekinitev, ki ga ta mehanizem odpravlja
Korak potrditve: MoveFileExW, write-through in ravnanje ob napaki
Zadnji korak vsakega shranjevanja je natanko en klic Windows API, MoveFileExW, z dvema zastavicama, ki opravita ločeni nalogi. MOVEFILE_REPLACE_EXISTING omogoči, da preimenovanje pristane na datoteki, ki že obstaja; brez njega preimenovanje na obstoječo pot preprosto spodleti, kar bi izničilo smisel shranjevanja, ki naj bi zamenjalo obstoječi delovni zvezek. MOVEFILE_WRITE_THROUGH skrbi za trajnost: funkciji naroči, naj se ne vrne, dokler premik ni dejansko končan na disku, namesto da bi se vrnila takoj, ko je preimenovanje le uvrščeno v čakalno vrsto, s čimer zapre ožje, vendar resnično časovno okno, v katerem bi sesutje takoj po vrnitvi SaveAs še lahko ujelo zamenjavo v teku. Če začasne datoteke ni mogoče ustvariti ali končno preimenovanje iz katerega koli razloga odpove, na primer zaradi dovoljenj, zaklenjenega cilja ali neusklajenosti nosilcev, HotXLS začasno datoteko izbriše sam, ciljna datoteka pa ostane natanko takšna, kot je bila pred klicem
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 sam ohranja skupno konvencijo vračanja v HotXLS, ena ob uspehu in negativno število ob neuspehu, vendar golo celo število ne pove, zakaj je shranjevanje spodletelo, enaka obravnava vseh negativnih rezultatov pa zavrže informacije, ki bi jih politika ponovnega poskusa lahko dejansko uporabila. Lastnost LastDiagnostic in podrobnejša zbirka Diagnostics za njo vsebujeta sporočilo, ki ga je HotXLS ustvaril interno, ter ločita začasno datoteko, ki je ni bilo mogoče ustvariti, od preimenovanja, ki ga je Windows zavrnil. Paketno opravilo, ki pri vsakem neuspelem klicu SaveAs zabeleži Code in Message, zbere natanko dokaze, ki jih želite imeti, ko stranka nekega dne sporoči, da shranjevanje ni naredilo ničesar
Klasični XLS plača s pomnilnikom, XLSX in ODS z diskom
Oba zapisovalnika shranjevanja dosežeta enak varen rezultat po različnih poteh, razlika pa je pomembna, če katerega že nastavljate za veliko paketno opravilo. Zapisovalnik klasičnega XLS najprej v celoti zgradi sestavljeni dokument OLE v pomnilniku, pri čemer uporablja strukturirano shrambo, podprto z ročajem pomnilnika, nato pa ta dokončani medpomnilnik z enim pisanjem kopira v sosednjo začasno datoteko; razlog v izvorni kodi HotXLS je neposreden: izdelava celotne datoteke v pomnilniku najprej prepreči, da bi neuspešno ali preklicano shranjevanje kdaj okrnilo cilj. Zapisovalnik XLSX in ODS namesto tega vnose ZIP pretaka v začasno datoteko, ko nastajajo, kar je enaka priprava na ravni datoteke z drugačnim pomnilniškim profilom. Če se že zanašate na StreamingWrite, da velike izvoze XLSX ohranite znotraj pomnilniške omejitve vsebnika, vedite, da enakovodna možnost za klasični izvoz XLS ne obstaja v isti obliki: jamstvo varnega shranjevanja je v obeh primerih brezpogojno, vendar zelo velik podedovani izvoz .xls ne glede na vse v RAM-u zadrži celoten izhod, kar je podrobneje obravnavano v našem članku o pretočnem pisanju za strežniška paketna opravila
Uporaba istega vzorca zunaj HotXLS in meje jamstva
Prevzem vzorca je večinoma stvar povezave istih dveh klicev Windows API, na katera se HotXLS interno zanaša. GetTempFileNameW vam izroči edinstveno poimenovano prazno datoteko v izbrani mapi, MoveFileExW pa vaš dokončani zapis z enim korakom potrdi čez pravi cilj; najmanjša različica iste rutine, ki jo HotXLS zažene pred vsakim SaveAs, je videti takole
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;
Jamstvo ima resnične meje, ki jih je vredno poznati, preden se nanj slepo zanesete. Priprava celotne kopije pred zamenjavo izvirnika pomeni, da shranjevanje za kratek čas potrebuje prostor za staro in novo datoteko, približno dvakratno velikost delovnega zvezka med pisanjem, kar je v redu za poročilo, preveriti pa je treba pri večgigabajtnem izvozu na skoraj polnem nosilcu. Začasna datoteka mora pristati tudi v isti mapi kot cilj, zato mora imeti račun, pod katerim teče HotXLS, dovoljenje za ustvarjanje datotek prav v tej mapi, ne le dovoljenja za prepis ene datoteke, ki jo že pozna; namestitev, ki ciljno mapo omeji na urejanje določenih obstoječih imen na mestu namesto dovoljenja za pisanje na ravni mape, bo pri koraku začasne datoteke videla neuspeh SaveAs, čeprav bi enakovredno neposredno pisanje uspelo
Jasno je treba omeniti še dve meji. Cilj na omrežni skupni rabi ali v mapi, ki jo sinhronizira OneDrive ali podoben odjemalec, se lahko obnaša drugače kot lokalni NTFS, čeprav ga Windows še vedno poroča kot en nosilec, saj gonilnik datotečnega sistema pred njim morda ne izvaja preimenovanja enako; če namestitev shranjuje na omrežno pot, je vredno tam posebej preizkusiti prisilno prekinitev, namesto da bi predpostavili prenos vedenja lokalnega diska. Celoten mehanizem je tudi omejen na shranjevanje v poimenovano datoteko. Če SaveAs namesto tega pokličete nad TStream, HotXLS neposredno piše v tok, ki ste mu ga predali, brez ciljne datoteke, ki bi jo bilo mogoče pripraviti ali zaščititi, saj je trajnost tega toka, naj gre za medpomnilnik v pomnilniku, omrežni prenos ali binarni objekt v zbirki podatkov, od te točke v celoti odgovornost vaše kode
Preverjanje se lahko nato zanese prav na to jamstvo, tudi pri preverjanju, vgrajenem v delovno okolje za pregled in pretvorbo delovnih zvezkov: če se znova odprta datoteka vrne okrnjena ali ji kaj manjka, gre za resnično težavo pri pretvorbi, ki jo je treba raziskati, nikoli pa za shranjevanje, prekinjeno na polovici, ki bi na disku pustilo dvoumno stanje. Varnostno pripravljeno pisanje je vključeno v SaveAs za vsak delovni zvezek XLSX, ODS in klasični XLS, ki ga za Delphi in C++Builder izdela HotXLS Component, brez potrebne konfiguracije za vklop