Spremanje koje se prekine na pola, bilo zbog prisilnog ponovnog pokretanja, zaustavljenog procesa ili diska koji se napuni usred zapisivanja, tradicionalno znači jedno za format izgrađen oko izravnog zapisivanja: dobijete upravo one bajtove koji su stigli na disk prije prekida, a skraćena se radna knjiga više ne otvara. HotXLS zatvara taj način kvara putem puta spremanja sigurnog nakon pada koji koristi za svaku XLSX, ODS i klasičnu XLS datoteku koju zapisuje. Svaki poziv SaveAs zapisuje cijelu novu datoteku u privremenu datoteku stvorenu uz odredište, a zatim je potvrđuje jednom atomskom operacijom MoveFileExW iz Windowsova API-ja, pa prekinuto spremanje može samo ne proizvesti novu datoteku, ali nikad ne oštećuje onu koju ste već imali. Ista disciplina pripremanja pa zamjene jednako se primjenjuje na oba HotXLS-ova mehanizma spremanja, BIFF8 zapisivač iza klasičnog XLS-a i OOXML zapisivač iza XLSX-a i ODS-a, a taj se obrazac isplati preuzeti za svaku datoteku koju vaš Delphi kod izravno prepisuje, bez obzira na to radi li se o proračunskim tablicama
Što se događa ako se spremanje radne knjige prekine na pola?
Izravan odgovor u potpunosti ovisi o tome kako zapisivač pristupa odredišnoj datoteci, a uobičajena implementacija, koja otvara ciljnu datoteku i izravno u nju usmjerava novi sadržaj, funkcionira sve dok ništa ne pođe po zlu. Čim se dogodi pad, prisilno zaustavljanje procesa ili prekid mrežnog dijeljenja usred zapisivanja, datoteka na disku ostaje u međustanju do kojeg je zapisivač stigao: središnji direktorij ZIP-a koji nikad nije dodan za XLSX ili ODS, ili BIFF tok kojem nedostaju zapisi koje čitač očekuje za klasični XLS. Excel to ne popravlja pouzdano, a ne radi to ni drugi potrošač koji očekuje potpunu datoteku, pa je praktičan rezultat radna knjiga koja se jučer normalno otvarala, a danas to odbija
Kako HotXLS priprema svako spremanje iza jedne atomske zamjene
HotXLS nikad izravno ne otvara odredišnu datoteku za zapisivanje, ni za jedan od triju formata koje sprema. Slijed je svaki put isti: izgradite potpuni izlaz na mjestu koje nije datoteka koju korisnik već ima na disku i premjestite ga na pravo mjesto tek kad je ta izrada u potpunosti uspješna. Konkretno, SaveAs stvara praznu privremenu datoteku u istoj mapi kao putanja odredišta, zapisuje cijelu novu radnu knjigu u tu privremenu datoteku i tek nakon što se zapisivanje vrati bez pogreške potvrđuje privremenu datoteku preko odredišta jednom operacijom preimenovanja. Za to nije potrebno uključiti neko svojstvo; tako SaveAs jednostavno 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 se disciplina primjenjuje i na zapisivač klasičnog XLS-a, ne samo na OOXML, a dvije privremene datoteke čak dijele konvenciju imenovanja: obje pozivaju Windowsov API GetTempFileNameW s prefiksom hxl, pa spremanje prekinuto prije čišćenja može ostaviti usamljenu datoteku s imenom poput hxl4C2A.tmp uz vašu radnu knjigu. Ta datoteka nije oštećenje, nego dokaz da je mehanizam radio točno prema dizajnu: nepotpuni se zapis zaustavio ondje, a vaša stvarna radna knjiga nikad nije bila otvorena za zapisivanje. Ako je vidite nakon pada, sigurno je možete izbrisati i nema potrebe za dodatnom istragom
Zašto privremenu datoteku stvarati uz radnu knjigu, a ne u %TEMP%?
Kratak odgovor glasi da je preimenovanje funkcije MoveFileExW atomsko samo kad se izvor i odredište nalaze na istom volumenu, a najsigurniji način da se to zajamči bez traženja dodatne konfiguracije od pozivatelja jest izvesti mjesto privremene datoteke iz same putanje odredišta. HotXLS izračunava mapu cilja i taj direktorij izravno predaje funkciji GetTempFileNameW, pa se privremena datoteka automatski stvara na istom pogonu i istom volumenu kao datoteka koju treba zamijeniti, pri svakom spremanju. Da je knjižnica umjesto toga pripremala zapis u sistemskoj privremenoj mapi, ciljna putanja na drugom pogonu ili mapirani mrežni volumen pretvorili bi završni korak u operaciju između volumena, što bi Windowsov API ili izričito odbio ili bi se, ako pozivatelj dodatno uključi zastavicu koju HotXLS ovdje ne postavlja, tiho pretvorilo u neatomsku kopiju pa brisanje, ponovno otvarajući upravo prozor prekida koji ovaj mehanizam treba zatvoriti
Korak potvrde: MoveFileExW, izravni zapis i postupanje pri pogrešci
Završni korak svakog spremanja točno je jedan poziv Windowsova API-ja, MoveFileExW, s dvjema zastavicama koje obavljaju različite poslove. MOVEFILE_REPLACE_EXISTING omogućuje da preimenovanje završi nad datotekom koja već postoji; bez nje preimenovanje usmjereno na postojeću putanju jednostavno ne uspijeva, što bi poništilo smisao spremanja kojim se zamjenjuje postojeća radna knjiga. MOVEFILE_WRITE_THROUGH odnosi se na trajnost: funkciji govori da se ne vrati dok premještanje stvarno nije dovršeno na disku, umjesto da se vrati čim je preimenovanje samo stavljeno u red, čime se zatvara uži, ali stvarni trk u kojem bi pad neposredno nakon povratka iz SaveAs-a još mogao zateći zamjenu u tijeku. Ako privremenu datoteku nije moguće stvoriti ili završno preimenovanje iz bilo kojeg razloga ne uspije, primjerice zbog dozvole, zaključanog odredišta ili neusklađenosti volumena, HotXLS sam briše privremenu datoteku umjesto da ostavi otpad, a odredišna datoteka ostaje točno onakva kakva je bila prije 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 povratnih vrijednosti u HotXLS-u, jedan za uspjeh i negativan broj za neuspjeh, ali običan cijeli broj ne govori zašto je spremanje neuspjelo, a jednako postupanje sa svakim negativnim rezultatom odbacuje informacije koje bi pravila za ponovni pokušaj mogla stvarno iskoristiti. Svojstvo LastDiagnostic i potpunija zbirka Diagnostics iza njega sadrže poruku koju je HotXLS interno generirao te razlikuju privremenu datoteku koja se nije mogla stvoriti od preimenovanja koje je Windows odbio. Grupni posao koji bilježi Code i Message pri svakom neuspjelom pozivu SaveAs gradi upravo dokaze koji su vam potrebni kad korisnik jednom prijavi da spremanje nije učinilo ništa
Klasični XLS plaća memorijom, XLSX i ODS plaćaju diskom
Dva mehanizma spremanja postižu isti rezultat siguran nakon pada različitim putovima, a ta je razlika važna ako već podešavate jedan od njih za veliki grupni posao. Zapisivač klasičnog XLS-a najprije u memoriji gradi cijeli OLE složeni dokument, koristeći strukturiranu pohranu oslonjenu na memorijski rukohvat, a zatim taj završeni međuspremnik jednim zapisom kopira u susjednu privremenu datoteku. Obrazloženje u izvornom kodu HotXLS-a izravno je: izgradnja cijele datoteke u memoriji unaprijed sprječava da neuspjelo ili otkazano spremanje ikad skrati odredište. Zapisivač XLSX-a i ODS-a umjesto toga zapisuje svoje ZIP unose u privremenu datoteku kako nastaju, što je isto pripremanje na razini datoteke uz drukčiji profil memorije. Ako se već oslanjate na StreamingWrite kako biste velike XLSX izvoze zadržali unutar memorijskog ograničenja spremnika, znajte da ekvivalentna poluga za izvoz klasičnog XLS-a ne postoji u istom obliku: jamstvo sigurnosti nakon pada u oba je slučaja bezuvjetno, ali vrlo velik naslijeđeni izvoz .xls-a svejedno drži cijeli izlaz u RAM-u, što je detaljnije obrađeno u našem članku o strujanom zapisivanju za poslužiteljske grupne poslove
Primjena istog obrasca izvan HotXLS-a i granice jamstva
Preuzimanje obrasca uglavnom se svodi na povezivanje istih dvaju Windowsovih API poziva na koje se HotXLS interno oslanja. GetTempFileNameW daje vam jedinstveno imenovanu praznu datoteku u mapi koju odaberete, a MoveFileExW u jednom koraku potvrđuje dovršeni zapis preko stvarnog odredišta; minimalna inačica iste rutine koju HotXLS pokreće prije svakog SaveAs-a 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;
Jamstvo ima stvarne granice koje vrijedi poznavati prije nego što se na njega slijepo oslonite. Pripremanje potpune kopije prije zamjene izvornika znači da spremanje nakratko treba prostor za staru i novu datoteku, otprilike dvostruku veličinu radne knjige tijekom zapisivanja, što je u redu za izvještaj, ali vrijedi provjeriti pri izvozu od više gigabajta na gotovo punom volumenu. Privremena datoteka također mora završiti u istoj mapi kao odredište, pa račun koji pokreće HotXLS mora imati dozvolu za stvaranje datoteka upravo u toj mapi, a ne samo dozvolu za prepisivanje jedne datoteke za koju već zna; implementacija koja mapu odredišta zaključava na izmjene postojećih imenovanih datoteka umjesto da omogući zapisivanje na razini mape vidjet će da SaveAs ne uspijeva pri koraku privremene datoteke, iako bi jednak izravan zapis uspio
Vrijedi jasno istaknuti još dvije granice. Odredište na mrežnom dijeljenju ili unutar mape koju sinkronizira OneDrive ili sličan klijent može se ponašati drukčije od lokalnog NTFS-a iako ga Windows i dalje prijavljuje kao jedan volumen, jer datotečni upravljački program ispred njega možda ne provodi preimenovanje na isti način. Ako vaše odredište za implementaciju sprema na mrežnu putanju, isplati se posebno ondje testirati prisilni prekid umjesto pretpostaviti da se ponašanje lokalnog diska prenosi. Cijeli mehanizam također je ograničen na spremanje u imenovanu datoteku. Ako umjesto toga pozovete SaveAs s objektom TStream, HotXLS izravno zapisuje u tok koji ste mu predali, bez odredišne datoteke koju bi trebalo pripremiti ili zaštititi, jer je trajnost tog toka, bilo da je riječ o memorijskom međuspremniku, mrežnom prijenosu ili binarnom objektu baze podataka, od tog trenutka odgovornost vašeg koda
Provjera se nakon toga može osloniti upravo na ovo jamstvo, uključujući onu ugrađenu u radni stol za reviziju i pretvorbu radnih knjiga: ponovno otvorena datoteka koja je skraćena ili joj nešto nedostaje stvaran je problem pretvorbe koji treba istražiti, a nikad spremanje prekinuto na pola koje je na disku ostavilo nešto nejasno. Pripremljeni zapisi sigurni nakon pada ugrađeni su u SaveAs za svaku XLSX, ODS i klasičnu XLS radnu knjigu koju proizvodi HotXLS Component za Delphi i C++Builder, bez ikakve konfiguracije za uključivanje