Čuvanje koje se prekine na pola, bilo zbog prinudnog ponovnog pokretanja, ubijenog procesa ili diska koji se napuni usred upisa, tradicionalno znači jedno za format zasnovan na upisu na mestu: dobijate one bajtove koji su stigli na disk pre prekida, a skraćena radna sveska više ne može da se otvori. HotXLS zatvara taj način otkaza putanjom za čuvanje otpornom na pad, koja se koristi za svaku XLSX, ODS i klasičnu XLS datoteku koju upisuje. Svaki poziv SaveAs upisuje celu novu datoteku u privremenu datoteku napravljenu pored odredišta, a zatim je potvrđuje jednom atomskom operacijom MoveFileExW preimenovanja iz Windows API-ja, pa prekinuto čuvanje može samo da ne proizvede novu datoteku, ali nikada ne oštećuje onu koju ste već imali. Ista disciplina pripreme pa zamene primenjuje se jednako u oba HotXLS mehanizma za čuvanje, BIFF8 upisivaču iza klasičnog XLS-a i OOXML upisivaču iza XLSX-a i ODS-a, a obrazac vredi primeniti na svaku datoteku koju vaš Delphi kod direktno prepisuje, bez obzira na to da li je tabela ili nešto drugo
Šta se dešava ako se čuvanje radne sveske prekine na pola?
Direktan odgovor u potpunosti zavisi od toga kako upisivač dodiruje odredišnu datoteku, a uobičajena implementacija, koja otvara ciljnu datoteku i direktno u nju upisuje novi sadržaj, dobra je sve dok ništa ne krene po zlu. Čim se desi pad, prinudno ubijanje procesa ili prekid mrežnog deljenog resursa usred upisa, datoteka na disku ostaje u međustanju do kog je upisivač stigao: ZIP centralni direktorijum koji nikada nije dodat kod XLSX-a ili ODS-a, ili BIFF tok kojem nedostaju zapisi koje čitač očekuje kod klasičnog XLS-a. Excel to ne popravlja pouzdano, kao ni bilo koji drugi korisnik koji očekuje potpunu datoteku, pa je praktičan rezultat radna sveska koja se juče normalno otvarala, a danas odbija da se otvori
Kako HotXLS priprema svako čuvanje iza jedne atomske zamene
HotXLS nikada ne otvara odredišnu datoteku direktno za upis, ni za jedan od tri formata koje čuva. Redosled je svaki put isti: izgradite potpun izlaz negde gde nije datoteka koju korisnik već ima na disku, a zatim ga postavite na odredište tek kada ta izgradnja potpuno uspe. Konkretno, SaveAs pravi praznu privremenu datoteku u istoj fascikli kao i ciljna putanja, upisuje celu novu radnu svesku u tu privremenu datoteku, a tek nakon što se upis završi bez greške potvrđuje privremenu datoteku preko odredišta jednom operacijom preimenovanja. Za to nije potrebno svojstvo koje biste uključili; to je jednostavno ono što SaveAs radi za običnu putanju datoteke pri svakom pozivu
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;
Ista disciplina važi i za klasični XLS upisivač, ne samo za OOXML, a dve privremene datoteke čak dele konvenciju imenovanja: obe pozivaju Windows API GetTempFileNameW sa prefiksom hxl, pa čuvanje prekinuto pre čišćenja može ostaviti zaostalu datoteku sa imenom poput hxl4C2A.tmp pored vaše radne sveske. Ta datoteka nije oštećenje, već dokaz da je mehanizam radio tačno kako je projektovano: nepotpun upis zaustavio se tamo, a vaša stvarna radna sveska nikada nije bila otvorena za upis. Ako se posle pada pojavi takva datoteka, bezbedno je obrisati i nema potrebe za daljim istraživanjem
Zašto se privremena datoteka pravi pored radne sveske, a ne u %TEMP%?
Kratak odgovor je da je preimenovanje MoveFileExW atomsko samo kada se izvor i odredište nalaze na istom volumenu, a najsigurniji način da se to garantuje bez traženja dodatnog podešavanja od pozivaoca jeste da se lokacija privremene datoteke izvede iz same odredišne putanje. HotXLS izračunava fasciklu cilja i prosleđuje taj direktorijum direktno funkciji GetTempFileNameW, pa se privremena datoteka pri svakom čuvanju automatski pravi na istom disku i istom volumenu kao datoteka koju treba zameniti. Kada bi biblioteka umesto toga pripremala upis u sistemskoj privremenoj fascikli, ciljna putanja na drugom disku ili mapirani mrežni volumen pretvorili bi završni korak u operaciju između volumena, koju Windows API ili odmah odbija ili, ako pozivalac izričito uključi dodatnu zastavicu koju HotXLS ovde ne postavlja, tiho pretvara u neatomsku kopiju praćenu brisanjem, čime bi se ponovo otvorio upravo prozor prekida koji ovaj mehanizam treba da zatvori
Korak potvrde: MoveFileExW, upis kroz sistem i šta se dešava pri grešci
Završni korak svakog čuvanja jeste tačno jedan poziv Windows API-ja, MoveFileExW, sa dve zastavice koje obavljaju različite poslove. MOVEFILE_REPLACE_EXISTING omogućava da preimenovanje stigne na putanju koja već postoji; bez njega preimenovanje usmereno na postojeću putanju jednostavno ne uspeva, čime bi se poništila cela svrha čuvanja koje treba da zameni radnu svesku koju već imate. MOVEFILE_WRITE_THROUGH obezbeđuje trajnost: govori funkciji da ne vrati rezultat dok se pomeranje zaista ne završi na disku, umesto da se vrati čim je preimenovanje samo stavljeno u red, čime se zatvara uži, ali stvaran uslov trke u kom bi pad neposredno nakon povratka iz SaveAs-a i dalje mogao da zatekne zamenu u toku. Ako privremena datoteka ne može da se napravi ili završno preimenovanje iz bilo kog razloga ne uspe, zbog problema sa dozvolama, zaključanog odredišta ili nepodudarnosti volumena, HotXLS sam briše privremenu datoteku umesto da ostavi otpad, a odredišna datoteka ostaje tačno onakva kakva je bila pre poziva
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;
Sam SaveAs koristi zajedničku konvenciju povratne vrednosti kroz HotXLS, jedan pri uspehu i negativan broj pri neuspehu, ali goli ceo broj ne govori zašto je čuvanje neuspešno, a tretiranje svakog negativnog rezultata na isti način odbacuje informacije koje bi politika ponovnog pokušaja mogla da iskoristi. Svojstvo LastDiagnostic i potpunija kolekcija Diagnostics iza njega nose poruku koju je HotXLS interno generisao i razlikuju neuspeh stvaranja privremene datoteke od preimenovanja koje je Windows odbio. Grupni posao koji beleži Code i Message pri svakom neuspešnom pozivu SaveAs gradi upravo dokaze koji su vam potrebni kada korisnik jednom prijavi čuvanje koje se tiho nije dogodilo
Klasični XLS plaća memorijom, a XLSX i ODS plaćaju diskom
Dva mehanizma za čuvanje dolaze do istog rezultata otpornog na pad različitim putevima, a razlika je važna ako neki od njih već podešavate za veliki grupni posao. Upisivač klasičnog XLS-a prvo gradi ceo OLE složeni dokument u memoriji, koristeći strukturisano skladište potpomognuto memorijskim rukohvatom, a zatim taj završeni bafer jednim upisom prebacuje u susednu privremenu datoteku; razlog je direktan: izgradnja cele datoteke u memoriji pre upisa sprečava da neuspešno ili otkazano čuvanje ikada skrati odredište. Upisivač XLSX-a i ODS-a umesto toga upisuje ZIP unose u privremenu datoteku dok nastaju, uz isto pripremanje na nivou datoteke i drugačiji profil memorije. Ako se već oslanjate na StreamingWrite da velike XLSX izvoze zadržite u granicama memorije kontejnera, imajte na umu da odgovarajuća poluga za izvoz klasičnog XLS-a ne postoji u istom obliku: garancija otpornosti na pad je bezuslovna u oba slučaja, ali veoma veliki nasleđeni .xls izvoz svakako drži ceo izlaz u RAM-u, što je detaljnije obrađeno u našem članku o strimovanom upisu za serverske grupne poslove
Primena istog obrasca izvan HotXLS-a i granice garancije
Preuzimanje obrasca uglavnom se svodi na povezivanje ista dva Windows API poziva na koja se HotXLS interno oslanja. GetTempFileNameW vam daje jedinstveno imenovanu praznu datoteku u fascikli koju izaberete, a MoveFileExW u jednom koraku potvrđuje završeni upis preko stvarnog odredišta; minimalna verzija iste rutine koju HotXLS izvršava pre svakog SaveAs poziva izgleda ovako
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;
Garancija ima stvarne granice koje vredi poznavati pre nego što se na nju slepo oslonite. Priprema pune kopije pre zamene originala znači da čuvanje nakratko zahteva prostor na disku i za staru i za novu datoteku, približno dvostruku veličinu radne sveske tokom upisa, što je u redu za izveštaj, ali vredi proveriti kod izvoza od više gigabajta na skoro punom volumenu. Privremena datoteka takođe mora da se smesti u istu fasciklu kao odredište, pa nalog pod kojim HotXLS radi mora imati dozvolu za stvaranje datoteka baš u toj fascikli, a ne samo dozvolu za prepisivanje jedne datoteke koju već poznaje; postavka koja zaključava odredišnu fasciklu na izmene konkretnih postojećih imena umesto da dozvoli upis na nivou fascikle doživeće neuspeh SaveAs-a u koraku privremene datoteke, iako bi odgovarajući direktni upis uspeo
Vredi jasno istaći još dve granice. Odredište na mrežnom deljenom resursu ili unutar fascikle koju sinhronizuje OneDrive ili sličan klijent može da se ponaša drugačije od lokalnog NTFS-a iako ga Windows i dalje prijavljuje kao jedan volumen, jer sistem datoteka ispred njega možda ne primenjuje preimenovanje na isti način; ako ciljna postavka čuva preko mrežne putanje, vredi baš tamo testirati prinudni prekid umesto pretpostaviti da se ponašanje lokalnog diska prenosi. Ceo mehanizam je, takođe, vezan za čuvanje u imenovanu datoteku. Ako umesto toga pozovete SaveAs nad TStream-om, HotXLS direktno upisuje u tok koji ste mu predali, bez odredišne datoteke koju bi pripremio ili zaštitio, jer je trajnost tog toka, bilo da je memorijski bafer, mrežni prenos ili binarni sadržaj u bazi podataka, od tog trenutka u potpunosti odgovornost vašeg koda
Provera posle toga može da se osloni upravo na ovu garanciju, uključujući onu ugrađenu u radni sto za proveru i konverziju radnih svezaka: ponovo otvorena datoteka koja se vrati skraćena ili bez sadržaja predstavlja stvaran problem konverzije koji treba istražiti, a ne čuvanje prekinuto na pola koje je ostavilo nejasno stanje na disku. Čuvanje kroz pripremljene privremene datoteke otporno na pad ugrađeno je u SaveAs za svaku XLSX, ODS i klasičnu XLS radnu svesku koju proizvodi HotXLS komponenta za Delphi i C++Builder, bez potrebe za dodatnim podešavanjem