A HotPDF RenderCacheFolder a HotPDF Delphi component memóriabeli, renderelt oldalakat tároló gyorsítótárát perzisztens lemezes oldal-gyorsítótárrá alakítja: a renderelt oldalak PNG fájlokként íródnak egy általad választott mappába, és amikor legközelebb ugyanaz a PDF forrás nyílik meg, a RenderLoadedPageToBitmapCached visszaolvassa őket ahelyett, hogy újra raszterezne. A keresési sorrend: memória, aztán lemez, aztán a renderer
A lemezes szint v2.416.0 óta ott van az API-ban, de v2.770.140-ig egy normál LoadFromFile vagy LoadFromStream hívás számára sosem szolgált ki ténylegesen oldalt. A javítás felvetette a kérdést, amire minden perzisztens gyorsítótárnak válaszolnia kell: honnan tudod, hogy a ma megnyitott fájl az a dokumentum, amit tegnap rendereltél, és mi történik a kapcsolt oldalakkal, ha nem az? Az alábbiakban azok a válaszok állnak, amiken a HotPDF megállapodott, beleszámítva, hol utasítja el szándékosan a kapcsolást
Hogyan működik a HotPDF lemezes render gyorsítótár?
A HotPDF lemezes render gyorsítótára a memóriabeli rasztergyorsítótár mögötti második szint, és csak akkor vesz részt a játékban, ha a RenderCacheFolder nem üres elérési út. Egy RenderLoadedPageToBitmapCached(PageIndex, DPI) hívás előbb a memóriabeli bejegyzéseket nézi át, amik oldalindex, DPI és egy render-beállításvariáns szerint kulcsoltak. Találatlan esetén a lemezes szintet kérdezi; egy lemezes találat dekódolja a PNG-t, visszateszi a memóriába és hívó tulajdonú másolatot ad. Csak ha mindkét szint találatlan, megy át az oldal a betöltött PDF oldal TBitmapra renderelését taglaló cikk tartalomfolyam-értelmezőjén, és a friss bitmap szintén lemezre íródik
Lemezen az elrendezés szándékosan unalmas. Minden dokumentum kap egy alszönyvtárt egy 16 hexakarakteres dokumentumkulcsból és egy 16 hexakarakteres render variánsból, minden oldal <page>@<dpi>.png néven tárolódik, és a gyökérben egy index.txt tartja a dokumentumokat legutóbb-használt sorrendben, egy schema tag mögött. Sémaeltérés első használatkor törli a mappát. Az írások előbb egy ideiglenes fájlba mennek, és atomi cserével kerülnek a helyükre, így egy írás közbeni összeomlás vagy a régi oldalt hagyja, vagy semmit, sosem fél PNG-t. A dekódolni nem tudó PNG törlődik, és találatlannak számolódik
Három limit határolja be a mappát:
- A
RenderCacheMaxDocuments(alapértéken 20) korlátozza a dokumentumalszönyvtárak számát; a legrégebben használt mappa távozik először - A
RenderCacheMaxBytes(alapértéken 524288000, ami 500 MB) korlátozza a gyökér alatti összes PNG fájl összméretét - Minden dokumentummappa legfeljebb 200 oldalképet tart; ezt az oldalankénti felső határt a THotPDF rögzíti, és nem publikált property
A RenderCacheCapacity (alapértéken 8) külön gomb: azt állítja, hány renderelt oldalt tart a memóriabeli szint, és a lemezes lábnyommal semmi köze nincs
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Állítsd be a lemezes szintet az első kapcsolt render előtt:
// a mappát és mindkét limitet a szint első használatakor olvassa
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // oldalak a memóriában
if Pdf.LoadFromFile(FileName) > 0 then
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 96);
if Bmp <> nil then
try
// Itt add át a másolatot a thumbnail sávnak
finally
Bmp.Free; // a kapcsolt hívás mindig hívó tulajdonú másolatot ad
end;
end;
finally
Pdf.Free; // v2.770.140 óta ez már nem törli a lemezes bejegyzéseket
end;
end;
Futtasd le ugyanazt az eljárást kétszer, és a második futás egyetlen, a gyorsítótárba férő oldalt sem raszterez újra. A lemezes gyorsítótár-objektum lustán születik az első kapcsolt rendernél, és a THotPDF példány felszabadításáig él, így a RenderCacheFolder, RenderCacheMaxDocuments vagy RenderCacheMaxBytes megváltoztatása ezután már nem mozgatja és nem méretezi át a nyitva lévő gyorsítótárat. Azok az oldalak, amik túl nagyok a memóriabeli felvételi politikához (alapértelmezés szerint egyetlen bejegyzés nem haladhatja meg a 32 bites pixelek 64 MiB-jét), szintén nem perzisztálódnak, és a lemezes szint csak addig kérdezett, amíg a RenderFallbackPolicy az alapértelmezett rfpIgnore-t tartja, mert a fallback diagnosztikák nem tárolódnak a PNG mellett
Miért nem működött sosem a RenderCacheFolder v2.770.140 előtt?
A RenderCacheFolder azért nem volt hatással v2.770.140 előtt, mert a lemezes szint a dokumentumokat a forrásbájtok hashéből kulcsolta, amit a hétköznapi betöltések sosem tartottak meg. A dokumentumkulcs a nyers PDF bájtok egy belső másolata felett számolt SHA-256-ból jött, de a LoadFromFile és a LoadFromStream a forrást helyben parzolja, és nem tart meg ilyen másolatot; a mező csak átmenetileg töltődött fel egy titkosított-helyreállítási úton, és rögtön utána törlődött is. Bájtok híján a kulcs mindig üres volt, az üres kulcs pedig azt jelenti, hogy a lemezes szint kikerül. Nincs hiba, nincs figyelmeztetés, csak egy üresen maradt mappa
A kulcs kitöltése előhozott egy második hibát, ami eddig az első mögött rejtőzött. A régi InvalidateRenderedPageCache törölte a dokumentum lemezes mappáját, az InvalidateRenderedPageCache pedig minden betöltés elején, minden szerkesztéskor és a Free-n belül is fut. Szóval abban a pillanatban, hogy a kulcs működött, minden nézegető munkamenet kilépéskor megsemmisítette volna a saját gyorsítótárát, és a következő munkamenet úgyis hidegen indult volna. Rosszabb: a kulcs egy szerkesztés után ugyanabból a forrásból számolódott újra, így a szerkesztett dokumentum renderjei az eredeti fájl kulcsa alatt tárolódtak volna, és a következő munkamenet, ami a módosítatlan PDF-et nyitja meg, ezeket kapta volna. A v2.770.140 az azonosságot és az érvénytelenítést együtt javítja; ha csak az egyiket javítják, vagy halott, vagy hazug gyorsítótár születik
Hogyan azonosítja a HotPDF a PDF-et a teljes fájl olvasása nélkül
A HotPDF egy helyi fájlból betöltött PDF-et a mérete, utolsó-írás ideje és első, utolsó 64 KiB-ja ujjlenyomatával azonosít, egy streamet vagy random-access forrást pedig a teljes tartalma SHA-256-ával. Mindkettő egyszer kapódik el, amikor egy betöltés sikerül, és a SHA-256 digest első 16 hexakaraktere (64 bit) lesz a dokumentumkulcs
| Forrás | Azonosság | Költség | Mikor kapódik el |
|---|---|---|---|
LoadFromFile | Méret + LastWriteTime + első és utolsó 64 KiB, SHA-256-gyal hashelve | Legfeljebb 128 KiB olvasás, a fájlmérettől függetlenül | Minden sikeres betöltés, akkor is, ha a RenderCacheFolder csak később állítódik |
LoadFromStream | A teljes stream SHA-256-a | Egy teljes átfutás a forráson | Csak ha a RenderCacheFolder a betöltés előtt állt be |
LoadFromRandomAccessSource | A teljes forrás SHA-256-a | Egy teljes átfutás a forráson | Csak ha a mappa előbb állt be, és a teljes tartomány elérhető |
Bármely /Encrypt bejegyzéses forrás | Nincs | Nincs | Soha; a lemezes szint kikerül |
A fájlujjlenyomat tudatos kompromisszum. Egy 400 MB-os szkenelt archívum teljes hashelése minden megnyitáskor többe kerülhet, mint annak a két oldalnak a renderelése, amit a felhasználó tényleg megnéz. A mintavett régiók nem véletlenszerűek: a fej a fájl elején ül, a trailer és az utolsó kereszthivatkozási szakasz pedig a végén (ISO 32000-1 §7.5). Egy inkrementális frissítés új testet, kereszthivatkozási szakaszt és trailert fűz hozzá (§7.5.6), így egyszerre változtatja meg a méretet és a farkat. Egy rendes eszköz teljes újraírása megváltoztatja az utolsó-írás időt. 128 KiB-ig terjedő fájloknál a két minta minden bájtot lefedi, így a kis dokumentumok gyakorlatilag teljes egészében hashelődnek
A maradék kockázat egy azonos méretű, helyben végzett módosítás egy nagy fájl közepén, aminek az írója aztán visszaállítja az eredeti időbélyeget. Ehhez olyan eszköz kell, ami szándékosan őrzi a módosítási időket, miközben a tartalmat szerkeszti, ami ritka, de nem lehetetlen, és ekkor a gyorsítótár elavult oldalakat szolgál ki. A másik oldal ártalmatlan: egy fájl lemásolása Windowson normál esetben megőrzi az utolsó-írás időt, így egy már gyorsítótárban lévő dokumentum másolata ugyanazokat a bejegyzéseket találja el, ami helyes, mert a bájtok azonosak
A streameknek egyáltalán nincs módosítási idejük, így az egyetlen becsületes azonosság a tartalom. A HotPDF csak akkor fizet a teljes SHA-256 átfutásért, ha betöltés előtt kértél lemezes gyorsítótárat; a LoadFromStream minden más hívója nem lát pluszköltséget. Ezért hordozó teher a property-hozzárendelés sorrendje:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Rossz sorrend streameknél: a tartalomhash csak akkor számolódik, ha a
// mappa már be van állítva, így ez a dokumentum átugraná a lemezes szintet
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // előbb állítsd be
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
Egy még letöltés alatt álló random-access forrás (néhány tartomány még nem elérhető) nem kap azonosságot, még részleges tartalom hashét sem, és ha az azonosság kiszámolása bármely okból elbukik, a betöltés akkor is sikerül; a dokumentum egyszerűen a lemezes szint nélkül renderel
Mi érvénytelenít egy HotPDF lemezes gyorsítótár-bejegyzést?
Egy HotPDF lemezes gyorsítótár-bejegyzést szerkesztéskor sosem törléssel érvénytelenítenek; ehelyett a betöltött dokumentum szerkesztése elveszi a dokumentumazonosságot, így a lemezes szint az adott betöltés további részére kikerül, a tárolt oldalak pedig érvényesek maradnak a módosítatlan forráshoz. A bejegyzések csak az LRU-n és a bájtlimiten át, egy sérült PNG-n vagy sémaváltáson át hagyják el a lemezt
A kulcs egy lemezen lévő forrást ír le, nem a memóriabeli objektumgráfot. Amint pecsételsz egy oldalt vagy megváltoztatsz egy annotációt, a dokumentum már nem egyezik azzal a forrással, így sem a kulcsa alatt olvasni, sem írni nem lenne helyes. v2.770.140 óta mind a dokumentumszintű, mind az oldalszintű érvénytelenítés az azonosságot törli a mappa érintése helyett, és van egy második őr is azokra a szerkesztésekre, amik nem hívták meg az InvalidateRenderedPageCache-t: mielőtt a lemezes szintet használná, a THotPDF megnézi, van-e dirty betöltött objektum, és egy dirty dokumentumot azonosság nélkülinek kezel
A render-beállítások fordítva működnek. A PageRenderBackend átkapcsolása (vagy a UseNativeGDIRenderBackend hívása), meg a ConfigureRenderICCWorkflow vagy ClearRenderICCWorkflow hívása kiüríti a memóriabeli oldalakat, de megtartja az azonosságot, mert a dokumentum továbbra is egyezik a forrásával. Ezek a beállítások a pixeleket változtatják meg anélkül, hogy a memóriabeli variáns részéi volnának, ezért a lemezes kulcs összefűzi a backend nevét, a black-point compensation flaget és az ICC proof meg kimeneti profilok SHA-256 digestjeit. Maga a variáns már lefedi a színszándékot, a kimeneti ditheringet, az overprint előnézetet, a luminosity maszk módot, a fallback politikát és minden opcionális tartalomcsoport láthatóságát, így egy réteg átkapcsolása másik mappába renderel az alapértelmezett nézet felülírása helyett
Hogy egy szerkesztett dokumentum visszakerüljön a lemezes szintre, adj neki új forrásazonosságot azzal, hogy elmented és visszatöltöd az eredményt:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// A betöltött dokumentum szerkesztése után: frissítsd a memóriabeli oldalakat.
// A forrásazonosság már elveszett, így az eredeti dokumentum lemezes
// mappájából semmi nem olvasódik és semmi nem íródik
Pdf.InvalidateRenderedPageCache;
// A mentett fájlnak új mérete és last-write ideje van, tehát új
// azonossága; a betöltés utáni renderek az új kulcs alá kerülnek
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Az eredeti dokumentum mappája békén marad, és a RenderCacheMaxDocuments és RenderCacheMaxBytes révén öregszik ki, mint bármely más bejegyzés. Ha a felhasználó újra megnyitja a szerkesztetlen eredetit, az oldalai még ott vannak
Biztonsági határok: titkosított források és linked mappák
A HotPDF lemezes render gyorsítótára szándékosan kétféle bemenetet utasít el: sosem ír lemezre titkosított PDF oldalait, és sosem követ egy junction vagy más reparse point dokumentumalszönyvtárt. Mindkét szabály gyorsítótár-találatokat ad oda azért, hogy ne szivárogjon adat és ne töröljön rossz fájlokat
Titkosított PDF-ek sosem kerülnek lemezre
Egy renderelt oldal dekódolt tartalom. Ha sima PNG-ként egy gyorsítótár-mappába írnád, jól olvasható másolata maradna egy jelszóval védett dokumentumnak a lemezen, a szerző által választott védelmen kívül (ISO 32000-1 §7.6). Ezért a HotPDF nem kap azonosságot bármely olyan forrásról, aminek a trailere /Encrypt bejegyzést hordoz, beleszámítva a jelszóval vagy üres user jelszóval nyitott fájlokat. Ezek a dokumentumok továbbra is a memóriabeli szintet használják, ami a folyamattal együtt hal
A junction alszönyvtárak v2.770.173 óta elutasítva
A gyorsítótár gyökere a te választásod, és junctionre mutatni vele megengedett. A felette lévő dokumentumalszönyvtárak már más kérdés: a gyorsítótár magától hozza létre, olvassa, érinti és törli őket, az indítási helyreállításnál (ami a maradék ideiglenes fájlokat szedi), a keresésnél (ami frissíti az időbélyegeket), a tárolásnál, az érvénytelenítésnél és a három kilakolási limitnél. Ha valaki, akinek írásjoga van a gyorsítótár gyökerére, egy dokumentummappát egy másik könyvtárra mutató junctionre cserél, mindegyik ilyen út követné, és a kilakolás ott törölne fájlokat, ahol a gyorsítótárnak sosem volt tulajdona. v2.770.173 óta mindegyik belépő ellenőrzi a reparse-point attribútumot, és átugorja a linked dokumentummappát: a keresés találatlannak számol, a tárolás íráshibának, a kilakolás pedig békén hagyja
Unicode elérési utak és megosztott gyökerek
Két rokon javítás számít, ha user profilokra telepítesz. v2.770.135 előtt a RenderCacheFolder AnsiString volt, így a rendszerkódlapon kívüli mappa (például kínai felhasználónév egy angol Windowson) veszteségesen alakult át, mielőtt a gyorsítótár meglátta volna; a property most Unicode string, és az atomi csere a széles Windows API-t használja. v2.770.52 óta több, egy folyamatban élő THotPDF példány, ami ugyanarra a gyökérre mutat (elérési út kibontása után, kisbetű-nagybetű érzéketlenül összevetve), egyetlen hivatkozásszámlált indexet és zárat oszt meg. Korábban minden példány a saját másolatával írta felül az index.txt-et, és a limiteket a részleges nézetére mérte, így a mappa a budgetjét többszörösen túl is nőhetett
Ez a megosztás a folyamathatárnál megáll. Két külön folyamat ugyanazon a gyökéren továbbra is külön memóriabeli indexeket tart, így adj minden párhuzamosan futó alkalmazásnak saját gyorsítótár-gyökeret. Azok a nézegetők, amik worker threadeken renderelnek, egy folyamaton belül rendben vannak: a PrefetchLoadedPages és a háttérrenderelés kérősorral cikkben taglalt sor ugyanazon a kapcsolt úton és ugyanazon a záron át megy
Gyorsreferencia: RenderCacheFolder ellenőrzőlista
- Állítsd be a
RenderCacheFolder-t,RenderCacheMaxDocuments-t ésRenderCacheMaxBytes-t az elsőRenderLoadedPageToBitmapCachedhívás előtt; stream és random-access betöltéseknél a mappát a betöltés előtt állítsd - Frissíts v2.770.140-re vagy újabbra, ha a lemezes szintre építesz; a korábbi verziók elfogadják a property-t, de normál betöltésekre sosem szolgálnak ki oldalt lemezről
- Számolj azzal, hogy titkosított PDF-ekre, a betöltés után szerkesztett dokumentumokra, vagy amíg a
RenderFallbackPolicynemrfpIgnore, nincs lemezes kapcsolás - Szabadítsd fel a THotPDF példányt rendesen; v2.770.140 óta sem a
Free, sem azInvalidateRenderedPageCachenem törli a lemezes bejegyzéseket - A
PageRenderBackendvagy az ICC munkafolyamat váltása a dokumentumot másik kulcs alatt megtartja a lemezes szinten - Futó alkalmazásonként egy gyorsítótár-gyökeret használj; egy folyamaton belüli példányok v2.770.52 óta megosztják az indexet
- Tartsd a gyorsítótár gyökerét userenkénti helyen; junction dokumentumalszönyvtárak v2.770.173 óta kihagyásra kerülnek
Egy perzisztens oldal-gyorsítótár leginkább olyan nézegetőben fizet ki, ami egész nap ugyanazokat a dokumentumokat nyitja újra, ami pontosan annak a egyéni PDF nézegető architektúrának Delphiben az alakja, amit a blog máshol taglal. A RenderCacheFolder, a memóriabeli rasztergyorsítótár és az oldalrenderer a HotPDF Delphi PDF component csomag része Delphihez és C++Builderhez