Teknisk artikkel

PDFium progressiv nedlasting og avbryt i Delphi (FPDFAvail)

PDFium Component åpner en PDF som fortsatt lastes ned gjennom TPdfProgressiveDocument, en TPdf-underklasse som pakker inn PDFiums FPDFAvail_*-tilgjengelighets-API. BeginProgressiveLoad starter økten, CheckDocumentAvailability rapporterer hvilke byte-områder PDFium fortsatt trenger, OpenProgressiveDocument åpner filen når nok byte finnes, og CancelProgressiveLoad forlater en avbrutt nedlasting uten å lekke opprinnelige håndtak. Den vanskelige delen er ikke den glatte veien. En viser på en ustabil forbindelse vil se brukere lukke fanen ved 25 prosent, ombestemme seg og åpne samme lenke igjen, og hver av de avbrutte øktene har et opprinnelig tilgjengelighetshåndtak, to C callback-recordser, en strømadapter og et sett med pågående rekkeviddeforespørsler som må slippes i nøyaktig riktig rekkefølge

Hvordan laster TPdfProgressiveDocument en PDF som fortsatt lastes ned?

TPdfProgressiveDocument holder en PDFium tilgjengelighetsleverandør i live mens en bare-tilgangsstrøm fylles, og spør leverandøren før hvert parsetrinn om bytene den vil ha, er til stede. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) tar bakgrunnsstrømmen pluss den logiske størrelsen til den eksterne filen, kobler en IsDataAvail-callback og en AddSegment-callback inn i to recordser, og kaller FPDFAvail_Create. Når PDFium spør om et område er til stede, svarer komponenten ja hvis området ligger innenfor det sammenhengende prefikset beskrevet av AvailableByteCount eller innenfor et område allerede fullført gjennom RangeRequests-planleggeren, og OnDataAvailable-event-en kan overstyre dommen for spredte lagre. Hvert kall til CheckDocumentAvailability gir én av tre TPdfDataAvailability-verdier (pdaAvailable, pdaNotAvailable, pdaError) og leverer områdene PDFium ba om som en sortert, flettet TPdfDownloadRanges-array, allerede i kø på planleggeren med rrpImmediate-prioritet

// FetchRange er transporten din (HTTP Range GET, socket, blob-leser):
// den skriver Size byte ved Offset inn i Store og gir tilbake hvor mange som kom frem
function FetchRange(Store: TStream; Offset, Size: UInt64): UInt64; forward;

procedure OpenWhileDownloading(Pdf: TPdfProgressiveDocument; Store: TStream;
  RemoteSize: UInt64);
const
  MaxRounds = 64;
var
  Hints: TPdfDownloadRanges;
  State: TPdfDataAvailability;
  Request: TPdfRangeRequest;
  Round: Integer;
begin
  Pdf.BeginProgressiveLoad(Store, RemoteSize, False);
  State := pdaNotAvailable;
  for Round := 1 to MaxRounds do
  begin
    State := Pdf.CheckDocumentAvailability(Hints);
    if State <> pdaNotAvailable then
      Break;
    // Hintene er allerede i kø; skriv bytene først, fullfør så
    while Pdf.RangeRequests.TryDequeue(Request) do
      Pdf.RangeRequests.CompleteRequest(Request,
        FetchRange(Store, Request.Offset, Request.Size));
  end;
  if State <> pdaAvailable then
    raise EPdfError.Create('The document could not be discovered');
  Pdf.OpenProgressiveDocument;
end;

To detaljer i den løkken bærer last. Rundegrensen betyr noe fordi en død lenke gjør at CheckDocumentAvailability spør etter de samme områdene for alltid, og en ubegrenset løkke gjør en nettverksfeil om til et hengende brukergrensesnitt. Rekkefølgen betyr noe fordi planleggeren serialiserer sin egen tilstand med en critical section, men ikke gjør noe for TStream.Position på bakgrunnslageret: en transporttråd må skrive responsbytene inn i strømmen før den kaller CompleteRequest, siden PDFium i det øyeblikket en fullføring publiseres, kan lese det området, og samtidige skrivere trenger posisjonert I/O eller en lås av egen

Tilgjengelighetsløkken til TPdfProgressiveDocument i PDFium Component: BeginProgressiveLoad lager FPDFAvail-leverandøren, CheckDocumentAvailability leverer sorterte flettede nedlastingshint i kø med rrpImmediate-prioritet, transporten skriver byte inn i lageret før CompleteRequest publiserer hvert område til PDFium, og løkken er begrenset til 64 runder fordi en død lenke spør etter de samme områdene om og om igjen
Skriv bytene, fullfør så forespørselen: i det øyeblikket en fullføring publiseres, kan PDFium lese det området, og ingenting beskytter strømposisjonen for deg

Hvorfor nekter AvailableByteCount å bevege seg baklengs?

AvailableByteCount vokser bare, og setteren reiser EPdfError med «Available byte count cannot move backwards» når du prøver å krympe den. Når IsDataAvail-callback-en har fortalt PDFium at et område finnes, kan parseren allerede ha lest og bufret objekter fra det, så å trekke de bytene etterpå ville gjort tilgjengelighetssvarene inkonsistente med det PDFium allerede har konsumert. Samme setter avviser verdier større enn LogicalFileSize og reiser «No progressive load is active» utenfor en økt, noe som er grunnen til at byte du allerede holder før lastingen starter, hører hjemme i AInitialAvailableByteCount-argumentet til BeginProgressiveLoad i stedet for i en egenskapstildeling gjort for tidlig. Fyller nedlastingslageret ditt ut av rekkefølge, ikke prøv å uttrykke det gjennom prefikset i det hele tatt: fullfør områdene gjennom planleggeren eller svar gjennom OnDataAvailable

Når kan en delvis nedlastet PDF faktisk åpnes?

Bare en linearisert PDF (ISO 32000-1 Annex F, «Fast Web View»-oppsettet) åpner før hele filen er kommet; en ikke-linearisert PDF trenger fortsatt hver byte. OpenProgressiveDocument sjekker Linearization-egenskapen (plnUnknown, plnNotLinearized, plnLinearized) og ruter deretter: en linearisert fil åpner gjennom FPDFAvail_GetDocument så snart førsteside-seksjonen og hint-tabellene er til stede, mens en ikke-linearisert fil åpnes gjennom FPDF_LoadCustomDocument på samme filtilgangs-record og behandles som lesbar bare som helhet. Rutingen finnes av en konkret grunn. Å kalle FPDFAvail_GetDocument på en ikke-linearisert fil kan gi et ikke-null håndtak hvis sideantall er null, et dokument som ser åpent ut og er tomt. I komponentens egen testpakke når en 51-siders linearisert fixture pdaAvailable og åpner med sitt fulle sidetre mens det spredte nedlastingslageret fortsatt ikke dekker filen

Hvordan OpenProgressiveDocument ruter en delvis nedlasting i PDFium Component: en linearisert fil åpner gjennom FPDFAvail_GetDocument når førsteside-seksjonen og hint-tabellene kommer, en ikke-linearisert fil trenger FPDF_LoadCustomDocument og hver byte, og LoadAvailablePage sjekker form-tilgjengelighet med FPDFAvail_IsFormAvail før sidesjekken, noe som unngår ikke-null-null-sidehåndtak-fellen
Bare lineariserte filer får et forsprang; på alt annet kan FPDFAvail_GetDocument gi et åpent-seende dokument med null sider, noe som er nøyaktig det rutingen forhindrer
function WaitForPage(Pdf: TPdfProgressiveDocument; Store: TStream;
  PageNumber: Integer): Boolean;
var
  Hints: TPdfDownloadRanges;
  Request: TPdfRangeRequest;
  Round: Integer;
begin
  Result := False;
  for Round := 1 to 64 do
    case Pdf.LoadAvailablePage(PageNumber, Hints) of
      pdaAvailable:
        Exit(True);   // PageNumber er nå den aktive siden
      pdaError:
        Exit(False);
      pdaNotAvailable:
        while Pdf.RangeRequests.TryDequeue(Request) do
          Pdf.RangeRequests.CompleteRequest(Request,
            FetchRange(Store, Request.Offset, Request.Size));
    end;
end;

LoadAvailablePage tar et 1-basert sidetall og håndhever rekkefølgen PDFium forventer: før den første sidesjekken kjører den CheckFormAvailability, som pakker inn FPDFAvail_IsFormAvail, og først etter det kaller den FPDFAvail_IsPageAvail. Et resultat av pfaNotPresent er det normale svaret for et dokument uten AcroForm og blokkerer ingenting. Når siden er klar, gjør LoadAvailablePage den til den aktive siden, så en viser kan rendere side 1 av en linearisert brosjyre mens de gjenværende sidene fortsatt er underveis; FirstAvailablePageNumber forteller deg hvilken side lineariseringsordboken utpeker som den første, allerede konvertert fra PDFiums nullbaserte indeks

Hva slipperr CancelProgressiveLoad, og i hvilken rekkefølge?

CancelProgressiveLoad river ned en økt i fire trinn som ikke kan omstokkes: avbryt rekkefølgeplanleggeren, lukk dokumentet, ødelegg tilgjengelighetshåndtaket med FPDFAvail_Destroy, og disponer så callback-recordsene og frigi strømadapteren. Å avbryte planleggeren først rykker generasjonstelleren dens fremover, slipper hver ventende og pågående forespørsel og fyrer av OnCancelRequest for hver pågående, så en transportfullføring som lander senere, bærer den gamle generasjonen og CompleteRequest gir False uten å røre noe. Dokumentet må lukkes før tilgjengelighetshåndtaket og adapteren forsvinner, for PDFium kan callbacke inn i filtilgangsleverandøren mens det lukker et dokument, og er adapteren allerede borte, leser den callbacken frigjort minne

Den faste nedrivningsrekkefølgen til CancelProgressiveLoad i PDFium Component: avbryt rekkefølgeplanleggeren først så sene fullføringer treffer den fremrykkede generasjonstelleren og gir False, lukk dokumentet før filtilgangsadapteren forsvinner, ødelegg tilgjengelighetshåndtaket med FPDFAvail_Destroy, og disponer så først callback-recordsene og frigi strømadapteren
Én idempotent metode rydder opp etter en feilet start, en brukeravbrudd og destruktoren alike; med en arbeidertråd som skriver inn i lageret, forblir strømeierskapet hos deg
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
  FPdf := TPdfProgressiveDocument.Create(nil);
  // Scheduleren lever like lenge som FPdf, så koble den én gang
  FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;

procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
  Attempt: Cardinal);
begin
  FTransport.Abort(RequestId);   // din kode: lukk den socketen eller forespørselen
end;

procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
  FPdf.CancelProgressiveLoad;
  // ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;

Metoden er idempotent og er den eneste oppryddingsveien for tre situasjoner: en BeginProgressiveLoad som feiler halvveis i konstruksjonen, en eksplisitt brukeravbrudd og destruktoren. BeginProgressiveLoad kaller den også før den starter, så å starte samme objekt på nytt på en ny URL er trygt uten en eksplisitt avbryting. Én eierskapsbeslutning er din å få riktig: skriver en arbeidertråd inn i bakgrunnsstrømmen, send AOwnsStream = False og frigi strømmen selv etter at arbeideren har stoppet, for med eierskapet overlatt frigjør avbrytingen strømmen mens en sen skriving fortsatt kan være på vei. Unntak reist inne i OnCancelRequest svelges per forespørsel, så én feilende transport kan ikke blokkere de gjenværende avbrytingene

Hvordan beviser livssykluspakken at avbrytingsveien ikke lekker?

PDFium Components livssyklusstresspakke utøver en avbrutt nettverksaktig nedlasting på hver blandet syklus. Hver syklus starter en progressiv lasting hvis lager holder bare en fjerdedel av fixture-bytene, krever pdaNotAvailable med en ikke-tom hint-liste, kaller CancelProgressiveLoad og assert at objektet verken rapporterer ProgressiveLoading eller Active; den kjører så samme strømningsvei til fullføring med full tilgjengelighet, OpenProgressiveDocument, en rendring og en lukking. Standard blandede kjøring dekker 100 målte sykluser med 600 åpninger, 2300 renderinger og 100 progressive avbrytinger, og samplet privat minne vokste med 8,21 MiB mot et budsjett på 32 MiB. Pakken teller progressive avbrytinger atskilt fra render-callback-avbrytinger, for en avbrutt nedlasting og en renderingsløkke som stopper tidlig, er forskjellige hendelser med forskjellige akseptkriterier

Der den progressive veien slutter å hjelpe

Noen få grenser er verdt å kjenne før du bygger en viser på toppen av dette. Funksjoner som trenger de opprinnelige filbytene avviser en ufullstendig progressiv kilde i stedet for å gjette: ReadXmpPacket feiler eksplisitt og signaturvalidering rapporterer Indeterminate til hele filen er til stede. Standard tilgjengelighetstest antar et sammenhengende prefiks, så en transport som henter områder ut av rekkefølge, må fullføre dem gjennom RangeRequests eller svare gjennom OnDataAvailable, ellers vil PDFium fortsette å spørre etter byte du allerede holder. En ikke-linearisert fil vinner ingenting i tid til første side, så hvis rask første maling betyr noe, lineariser filen på serversiden. Og CancelProgressiveLoad lukker ikke socketene dine av seg selv; OnCancelRequest er kroken der det skjer

For den rene strømadapter-veien som laster en komplett lokal fil på forespørsel, se strømming av store PDF-er på forespørsel med PDFium; for å åpne en PDF som ligger inne i en større buffer, se byte range-lasting for innebygde PDF-er. Å avbryte en treg rendring av en side som allerede er lastet, er en egen mekanisme, dekket i avbrytbar progressiv siderendring. TPdfProgressiveDocument og rekkefølgeplanleggeren dens følger med PDFium Component for Delphi og C++Builder