bufstream.pp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. {
  2. This file is part of the Free Component Library.
  3. Copyright (c) 1999-2000 by the Free Pascal development team
  4. Implement a buffered stream.
  5. TBufferedFileStream contributed by José Mejuto, bug ID 30549.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$mode objfpc}
  13. {$H+}
  14. unit bufstream;
  15. interface
  16. uses
  17. Classes, SysUtils;
  18. Const
  19. DefaultBufferCapacity : Integer = 16; // Default buffer capacity in Kb.
  20. Type
  21. { ---------------------------------------------------------------------
  22. TBufStream - simple read or write buffer, for sequential reading/writing
  23. ---------------------------------------------------------------------}
  24. TBufStream = Class(TOwnerStream)
  25. Private
  26. FTotalPos : Int64;
  27. Fbuffer: Pointer;
  28. FBufPos: Integer;
  29. FBufSize: Integer;
  30. FCapacity: Integer;
  31. procedure SetCapacity(const AValue: Integer);
  32. Protected
  33. function GetPosition: Int64; override;
  34. function GetSize: Int64; override;
  35. procedure BufferError(const Msg : String);
  36. Procedure FillBuffer; Virtual;
  37. Procedure FlushBuffer; Virtual;
  38. Public
  39. Constructor Create(ASource : TStream; ACapacity: Integer);
  40. Constructor Create(ASource : TStream);
  41. Destructor Destroy; override;
  42. Property Buffer : Pointer Read Fbuffer;
  43. Property Capacity : Integer Read FCapacity Write SetCapacity;
  44. Property BufferPos : Integer Read FBufPos; // 0 based.
  45. Property BufferSize : Integer Read FBufSize; // Number of bytes in buffer.
  46. end;
  47. { TReadBufStream }
  48. TReadBufStream = Class(TBufStream)
  49. Public
  50. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  51. Function Read(var ABuffer; ACount : LongInt) : Integer; override;
  52. end;
  53. { TWriteBufStream }
  54. TWriteBufStream = Class(TBufStream)
  55. Public
  56. Destructor Destroy; override;
  57. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  58. Function Write(Const ABuffer; ACount : LongInt) : Integer; override;
  59. end;
  60. { ---------------------------------------------------------------------
  61. TBufferedFileStream -
  62. Multiple pages buffer for random access reading/writing in file.
  63. ---------------------------------------------------------------------}
  64. TBufferedFileStream = class(TFileStream)
  65. private
  66. const
  67. TSTREAMCACHEPAGE_SIZE_DEFAULT=4*1024;
  68. TSTREAMCACHEPAGE_MAXCOUNT_DEFAULT=8;
  69. type
  70. TStreamCacheEntry=record
  71. IsDirty: Boolean;
  72. LastTick: NativeUInt;
  73. PageBegin: int64;
  74. PageRealSize: integer;
  75. Buffer: Pointer;
  76. end;
  77. PStreamCacheEntry=^TStreamCacheEntry;
  78. private
  79. FCachePages: array of PStreamCacheEntry;
  80. FCacheLastUsedPage: integer;
  81. FCacheStreamPosition: int64;
  82. FCacheStreamSize: int64;
  83. FOpCounter: NativeUInt;
  84. FStreamCachePageSize: integer;
  85. FStreamCachePageMaxCount: integer;
  86. FEmergencyFlag: Boolean;
  87. procedure ClearCache;
  88. procedure WriteDirtyPage(const aPage: PStreamCacheEntry);
  89. procedure WriteDirtyPage(const aIndex: integer);
  90. procedure WriteDirtyPages;
  91. procedure EmergencyWriteDirtyPages;
  92. procedure FreePage(const aPage: PStreamCacheEntry; const aFreeBuffer: Boolean); inline;
  93. function LookForPositionInPages: Boolean;
  94. function ReadPageForPosition: Boolean;
  95. function ReadPageBeforeWrite: Boolean;
  96. function FreeOlderInUsePage(const aFreeBuffer: Boolean=false): PStreamCacheEntry;
  97. function GetOpCounter: NativeUInt; inline;
  98. function DoCacheRead(var Buffer; Count: Longint): Longint;
  99. function DoCacheWrite(const Buffer; Count: Longint): Longint;
  100. protected
  101. function GetPosition: Int64; override;
  102. procedure SetPosition(const Pos: Int64); override;
  103. function GetSize: Int64; override;
  104. procedure SetSize64(const NewSize: Int64); override;
  105. procedure SetSize(NewSize: Longint); override;overload;
  106. procedure SetSize(const NewSize: Int64); override;overload;
  107. public
  108. // Warning using Mode=fmOpenWrite because the write buffer
  109. // needs to read, as this class is a cache system not a dumb buffer.
  110. constructor Create(const AFileName: string; Mode: Word);
  111. constructor Create(const AFileName: string; Mode: Word; Rights: Cardinal);
  112. destructor Destroy; override;
  113. function Seek(Offset: Longint; Origin: Word): Longint; override; overload;
  114. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override; overload;
  115. function Read(var Buffer; Count: Longint): Longint; override;
  116. function Write(const Buffer; Count: Longint): Longint; override;
  117. // Flush write-cache content to disk
  118. procedure Flush;
  119. // re-initialize the cache with aCacheBlockCount block
  120. // of aCacheBlockSize bytes in each block.
  121. procedure InitializeCache(const aCacheBlockSize: integer; const aCacheBlockCount: integer);
  122. end;
  123. implementation
  124. Resourcestring
  125. SErrCapacityTooSmall = 'Capacity is less than actual buffer size.';
  126. SErrCouldNotFLushBuffer = 'Could not flush buffer';
  127. SErrInvalidSeek = 'Invalid buffer seek operation';
  128. SErrCacheUnexpectedPageDiscard ='CACHE: Unexpected behaviour. Discarded page.';
  129. SErrCacheUnableToReadExpected = 'CACHE: Unable to read expected bytes (Open for write only ?). Expected: %d, effective read: %d';
  130. SErrCacheUnableToWriteExpected ='CACHE: Unable to write expected bytes (Open for read only ?). Expected: %d, effective write: %d';
  131. SErrCacheInternal = 'CACHE: Internal error.';
  132. { TBufStream }
  133. procedure TBufStream.SetCapacity(const AValue: Integer);
  134. begin
  135. if (FCapacity<>AValue) then
  136. begin
  137. If (AValue<FBufSize) then
  138. BufferError(SErrCapacityTooSmall);
  139. ReallocMem(FBuffer,AValue);
  140. FCapacity:=AValue;
  141. end;
  142. end;
  143. function TBufStream.GetPosition: Int64;
  144. begin
  145. Result:=FTotalPos;
  146. end;
  147. function TBufStream.GetSize: Int64;
  148. begin
  149. Result:=Source.Size;
  150. end;
  151. procedure TBufStream.BufferError(const Msg: String);
  152. begin
  153. Raise EStreamError.Create(Msg);
  154. end;
  155. procedure TBufStream.FillBuffer;
  156. Var
  157. RCount : Integer;
  158. P : PAnsiChar;
  159. begin
  160. P:=PAnsiChar(FBuffer);
  161. // Reset at beginning if empty.
  162. If (FBufSize-FBufPos)<=0 then
  163. begin
  164. FBufSize:=0;
  165. FBufPos:=0;
  166. end;
  167. Inc(P,FBufSize);
  168. RCount:=1;
  169. while (RCount<>0) and (FBufSize<FCapacity) do
  170. begin
  171. RCount:=FSource.Read(P^,FCapacity-FBufSize);
  172. Inc(P,RCount);
  173. Inc(FBufSize,RCount);
  174. end;
  175. end;
  176. procedure TBufStream.FlushBuffer;
  177. Var
  178. WCount : Integer;
  179. P : PAnsiChar;
  180. begin
  181. P:=PAnsiChar(FBuffer);
  182. Inc(P,FBufPos);
  183. WCount:=1;
  184. While (WCount<>0) and ((FBufSize-FBufPos)>0) do
  185. begin
  186. WCount:=FSource.Write(P^,FBufSize-FBufPos);
  187. Inc(P,WCount);
  188. Inc(FBufPos,WCount);
  189. end;
  190. If ((FBufSize-FBufPos)<=0) then
  191. begin
  192. FBufPos:=0;
  193. FBufSize:=0;
  194. end
  195. else
  196. BufferError(SErrCouldNotFLushBuffer);
  197. end;
  198. constructor TBufStream.Create(ASource: TStream; ACapacity: Integer);
  199. begin
  200. Inherited Create(ASource);
  201. SetCapacity(ACapacity);
  202. end;
  203. constructor TBufStream.Create(ASource: TStream);
  204. begin
  205. Create(ASource,DefaultBufferCapacity*1024);
  206. end;
  207. destructor TBufStream.Destroy;
  208. begin
  209. FBufSize:=0;
  210. SetCapacity(0);
  211. inherited Destroy;
  212. end;
  213. { TReadBufStream }
  214. function TReadBufStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  215. begin
  216. FakeSeekForward(Offset,Origin,FTotalPos);
  217. Result:=FTotalPos; // Pos updated by fake read
  218. end;
  219. function TReadBufStream.Read(var ABuffer; ACount: LongInt): Integer;
  220. Var
  221. P,PB : PAnsiChar;
  222. Avail,MSize,RCount : Integer;
  223. begin
  224. Result:=0;
  225. P:=PAnsiChar(@ABuffer);
  226. Avail:=1;
  227. While (Result<ACount) and (Avail>0) do
  228. begin
  229. If (FBufSize-FBufPos<=0) then
  230. FillBuffer;
  231. Avail:=FBufSize-FBufPos;
  232. If (Avail>0) then
  233. begin
  234. MSize:=ACount-Result;
  235. If (MSize>Avail) then
  236. MSize:=Avail;
  237. PB:=PAnsiChar(FBuffer);
  238. Inc(PB,FBufPos);
  239. Move(PB^,P^,MSIze);
  240. Inc(FBufPos,MSize);
  241. Inc(P,MSize);
  242. Inc(Result,MSize);
  243. end;
  244. end;
  245. Inc(FTotalPos,Result);
  246. end;
  247. { TWriteBufStream }
  248. destructor TWriteBufStream.Destroy;
  249. begin
  250. FlushBuffer;
  251. inherited Destroy;
  252. end;
  253. function TWriteBufStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  254. begin
  255. if (Offset=0) and (Origin=soCurrent) then
  256. Result := FTotalPos
  257. else
  258. BufferError(SErrInvalidSeek);
  259. end;
  260. function TWriteBufStream.Write(const ABuffer; ACount: LongInt): Integer;
  261. Var
  262. P,PB : PAnsiChar;
  263. Avail,MSize,RCount : Integer;
  264. begin
  265. Result:=0;
  266. P:=PAnsiChar(@ABuffer);
  267. While (Result<ACount) do
  268. begin
  269. If (FBufSize=FCapacity) then
  270. FlushBuffer;
  271. Avail:=FCapacity-FBufSize;
  272. MSize:=ACount-Result;
  273. If (MSize>Avail) then
  274. MSize:=Avail;
  275. PB:=PAnsiChar(FBuffer);
  276. Inc(PB,FBufSize);
  277. Move(P^,PB^,MSIze);
  278. Inc(FBufSize,MSize);
  279. Inc(P,MSize);
  280. Inc(Result,MSize);
  281. end;
  282. Inc(FTotalPos,Result);
  283. end;
  284. { ---------------------------------------------------------------------
  285. TBufferedFileStream
  286. ---------------------------------------------------------------------}
  287. procedure TBufferedFileStream.ClearCache;
  288. var
  289. j: integer;
  290. pStream: PStreamCacheEntry;
  291. begin
  292. try
  293. WriteDirtyPages;
  294. finally
  295. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  296. pStream:=FCachePages[j];
  297. if Assigned(pStream) then begin
  298. if Assigned(pStream^.Buffer) then Freemem(pStream^.Buffer);
  299. Dispose(pStream);
  300. FCachePages[j]:=nil;
  301. end;
  302. end;
  303. end;
  304. end;
  305. procedure TBufferedFileStream.WriteDirtyPage(const aPage: PStreamCacheEntry);
  306. var
  307. lEffectiveBytesWrite: integer;
  308. begin
  309. inherited Seek(aPage^.PageBegin,soBeginning);
  310. lEffectiveBytesWrite:=inherited Write(aPage^.Buffer^,aPage^.PageRealSize);
  311. if lEffectiveBytesWrite<>aPage^.PageRealSize then begin
  312. EmergencyWriteDirtyPages;
  313. Raise EStreamError.CreateFmt(SErrCacheUnableToWriteExpected,[aPage^.PageRealSize,lEffectiveBytesWrite,IntToStr(aPage^.PageBegin)]);
  314. end;
  315. aPage^.IsDirty:=False;
  316. aPage^.LastTick:=GetOpCounter;
  317. end;
  318. procedure TBufferedFileStream.WriteDirtyPage(const aIndex: integer);
  319. var
  320. pCache: PStreamCacheEntry;
  321. begin
  322. pCache:=FCachePages[aIndex];
  323. if Assigned(pCache) then begin
  324. WriteDirtyPage(pCache);
  325. end;
  326. end;
  327. procedure TBufferedFileStream.WriteDirtyPages;
  328. var
  329. j: integer;
  330. pCache: PStreamCacheEntry;
  331. begin
  332. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  333. pCache:=FCachePages[j];
  334. if Assigned(pCache) then begin
  335. if pCache^.IsDirty then begin
  336. WriteDirtyPage(pCache);
  337. end;
  338. end;
  339. end;
  340. end;
  341. procedure TBufferedFileStream.EmergencyWriteDirtyPages;
  342. var
  343. j: integer;
  344. pCache: PStreamCacheEntry;
  345. begin
  346. // Are we already in a emergency write dirty pages ??
  347. if FEmergencyFlag then exit;
  348. FEmergencyFlag:=true;
  349. // This procedure tries to save all dirty pages inconditional
  350. // because a write fail happens, so everything in cache will
  351. // be dumped to stream if possible, trying to save as much
  352. // information as possible.
  353. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  354. pCache:=FCachePages[j];
  355. if Assigned(pCache) then begin
  356. if pCache^.IsDirty then begin
  357. try
  358. WriteDirtyPage(pCache);
  359. except on e: Exception do begin
  360. // Do nothing, eat exception if happen.
  361. // This way the cache still holds data to be
  362. // written (that fails) and can be written later
  363. // if write fail conditions change.
  364. end;
  365. end;
  366. end;
  367. end;
  368. end;
  369. FEmergencyFlag:=False;
  370. end;
  371. procedure TBufferedFileStream.FreePage(const aPage: PStreamCacheEntry;
  372. const aFreeBuffer: Boolean);
  373. begin
  374. aPage^.PageBegin:=0;
  375. aPage^.PageRealSize:=0;
  376. aPage^.LastTick:=0;
  377. aPage^.IsDirty:=false;
  378. if aFreeBuffer then begin
  379. FreeMem(aPage^.Buffer);
  380. aPage^.Buffer:=nil;
  381. end;
  382. end;
  383. function TBufferedFileStream.LookForPositionInPages: Boolean;
  384. var
  385. j: integer;
  386. pCache: PStreamCacheEntry;
  387. begin
  388. Result:=false;
  389. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  390. pCache:=FCachePages[j];
  391. if Assigned(pCache^.Buffer) then begin
  392. if (FCacheStreamPosition>=pCache^.PageBegin) and (FCacheStreamPosition<pCache^.PageBegin+pCache^.PageRealSize) then begin
  393. FCacheLastUsedPage:=j;
  394. Result:=true;
  395. exit;
  396. end;
  397. end;
  398. end;
  399. end;
  400. function TBufferedFileStream.ReadPageForPosition: Boolean;
  401. var
  402. j: integer;
  403. pCache: PStreamCacheEntry=nil;
  404. lStreamPosition: int64;
  405. begin
  406. // Find free page entry
  407. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  408. if not Assigned(FCachePages[j]^.Buffer) then begin
  409. pCache:=FCachePages[j];
  410. FCacheLastUsedPage:=j;
  411. break;
  412. end;
  413. end;
  414. if not Assigned(pCache) then begin
  415. // Free last used page
  416. pCache:=FreeOlderInUsePage(false);
  417. end;
  418. if not Assigned(pCache^.Buffer) then begin
  419. Getmem(pCache^.Buffer,FStreamCachePageSize);
  420. end;
  421. lStreamPosition:=(FCacheStreamPosition div FStreamCachePageSize)*FStreamCachePageSize;
  422. inherited Seek(lStreamPosition,soBeginning);
  423. pCache^.PageBegin:=lStreamPosition;
  424. pCache^.PageRealSize:=inherited Read(pCache^.Buffer^,FStreamCachePageSize);
  425. if pCache^.PageRealSize=FStreamCachePageSize then begin
  426. pCache^.LastTick:=GetOpCounter;
  427. Result:=true;
  428. end else begin
  429. if FCacheStreamPosition<lStreamPosition+pCache^.PageRealSize then begin
  430. pCache^.LastTick:=GetOpCounter;
  431. Result:=true;
  432. end else begin
  433. Result:=false;
  434. end;
  435. end;
  436. end;
  437. function TBufferedFileStream.ReadPageBeforeWrite: Boolean;
  438. var
  439. j: integer;
  440. pCache: PStreamCacheEntry=nil;
  441. lStreamPosition: int64;
  442. lExpectedBytesToRead: integer;
  443. lEffectiveRead: integer;
  444. begin
  445. // Find free page entry
  446. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  447. if not Assigned(FCachePages[j]^.Buffer) then begin
  448. pCache:=FCachePages[j];
  449. FCacheLastUsedPage:=j;
  450. break;
  451. end;
  452. end;
  453. if not Assigned(pCache) then begin
  454. // Free last used page
  455. pCache:=FreeOlderInUsePage(false);
  456. end;
  457. if not Assigned(pCache^.Buffer) then begin
  458. Getmem(pCache^.Buffer,FStreamCachePageSize);
  459. end;
  460. lStreamPosition:=(FCacheStreamPosition div FStreamCachePageSize)*FStreamCachePageSize;
  461. inherited Seek(lStreamPosition,soBeginning);
  462. if (lStreamPosition+FStreamCachePageSize) > FCacheStreamSize then begin
  463. lExpectedBytesToRead:=FCacheStreamSize-lStreamPosition;
  464. end else begin
  465. lExpectedBytesToRead:=FStreamCachePageSize;
  466. end;
  467. pCache^.PageBegin:=lStreamPosition;
  468. pCache^.PageRealSize:=inherited Read(pCache^.Buffer^,FStreamCachePageSize);
  469. if pCache^.PageRealSize<>lExpectedBytesToRead then begin
  470. lEffectiveRead:=pCache^.PageRealSize;
  471. pCache^.IsDirty:=false;
  472. pCache^.LastTick:=0;
  473. pCache^.PageBegin:=0;
  474. pCache^.PageRealSize:=0;
  475. Freemem(pCache^.Buffer);
  476. pCache^.Buffer:=nil;
  477. Raise EStreamError.CreateFmt(SErrCacheUnableToReadExpected,[lExpectedBytesToRead,lEffectiveRead]);
  478. end;
  479. pCache^.LastTick:=GetOpCounter;
  480. Result:=true;
  481. end;
  482. function TBufferedFileStream.FreeOlderInUsePage(const aFreeBuffer: Boolean
  483. ): PStreamCacheEntry;
  484. var
  485. j: integer;
  486. lOlderTick: int64=High(int64);
  487. lOlderEntry: integer=-1;
  488. begin
  489. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  490. Result:=FCachePages[j];
  491. if Assigned(Result^.Buffer) then begin
  492. if Result^.LastTick<lOlderTick then begin
  493. lOlderTick:=Result^.LastTick;
  494. lOlderEntry:=j;
  495. end;
  496. end;
  497. end;
  498. if lOlderEntry=-1 then begin
  499. Raise Exception.Create(SErrCacheInternal);
  500. end;
  501. Result:=FCachePages[lOlderEntry];
  502. FCacheLastUsedPage:=lOlderEntry;
  503. if Result^.IsDirty then begin
  504. WriteDirtyPage(Result);
  505. end;
  506. FreePage(Result,aFreeBuffer);
  507. end;
  508. function TBufferedFileStream.GetOpCounter: NativeUInt;
  509. begin
  510. Result:=FOpCounter;
  511. {$PUSH}
  512. {$Q-}
  513. inc(FOpCounter);
  514. {$POP}
  515. end;
  516. function TBufferedFileStream.DoCacheRead(var Buffer; Count: Longint): Longint;
  517. var
  518. pCache: PStreamCacheEntry;
  519. lAvailableInThisPage: integer;
  520. lPositionInPage: integer;
  521. lNewBuffer: PBYTE;
  522. begin
  523. pCache:=FCachePages[FCacheLastUsedPage];
  524. if Assigned(pCache) then begin
  525. // Check if FCacheStreamPosition is in range
  526. if Assigned(pCache^.Buffer) then begin
  527. if (FCacheStreamPosition>=pCache^.PageBegin) and (FCacheStreamPosition<pCache^.PageBegin+pCache^.PageRealSize) then begin
  528. // Position is in range, so read available data from this page up to count or page end
  529. lPositionInPage:=(FCacheStreamPosition-pCache^.PageBegin);
  530. lAvailableInThisPage:=pCache^.PageRealSize - lPositionInPage;
  531. if lAvailableInThisPage>=Count then begin
  532. move((PBYTE(pCache^.Buffer)+lPositionInPage)^,Buffer,Count);
  533. inc(FCacheStreamPosition,Count);
  534. Result:=Count;
  535. pCache^.LastTick:=GetOpCounter;
  536. exit;
  537. end else begin
  538. move((PBYTE(pCache^.Buffer)+lPositionInPage)^,Buffer,lAvailableInThisPage);
  539. inc(FCacheStreamPosition,lAvailableInThisPage);
  540. if pCache^.PageRealSize=FStreamCachePageSize then begin
  541. lNewBuffer:=PBYTE(@Buffer)+lAvailableInThisPage;
  542. Result:=lAvailableInThisPage+DoCacheRead(lNewBuffer^,Count-lAvailableInThisPage);
  543. end else begin
  544. // This cache page is not filled, so it is the last one
  545. // in the file, nothing more to read...
  546. pCache^.LastTick:=GetOpCounter;
  547. Result:=lAvailableInThisPage;
  548. end;
  549. exit;
  550. end;
  551. end else begin
  552. // The position is in other cache page or not in cache at all, so look for
  553. // position in cached pages or allocate a new page.
  554. if LookForPositionInPages then begin
  555. Result:=DoCacheRead(Buffer,Count);
  556. exit;
  557. end else begin
  558. if ReadPageForPosition then begin
  559. Result:=DoCacheRead(Buffer,Count);
  560. end else begin
  561. Result:=0;
  562. end;
  563. exit;
  564. end;
  565. end;
  566. end else begin
  567. if ReadPageForPosition then begin
  568. Result:=DoCacheRead(Buffer,Count);
  569. end else begin
  570. Result:=0;
  571. end;
  572. exit;
  573. end;
  574. end else begin
  575. // The page has been discarded for some unknown reason
  576. Raise EStreamError.Create(SErrCacheUnexpectedPageDiscard);
  577. end;
  578. end;
  579. function TBufferedFileStream.DoCacheWrite(const Buffer; Count: Longint): Longint;
  580. var
  581. pCache: PStreamCacheEntry;
  582. lAvailableInThisPage: integer;
  583. lPositionInPage: integer;
  584. lNewBuffer: PBYTE;
  585. begin
  586. pCache:=FCachePages[FCacheLastUsedPage];
  587. if Assigned(pCache) then begin
  588. // Check if FCacheStreamPosition is in range
  589. if Assigned(pCache^.Buffer) then begin
  590. if (FCacheStreamPosition>=pCache^.PageBegin) and (FCacheStreamPosition<pCache^.PageBegin+FStreamCachePageSize) then begin
  591. // Position is in range, so write data up to end of page
  592. lPositionInPage:=(FCacheStreamPosition-pCache^.PageBegin);
  593. lAvailableInThisPage:=FStreamCachePageSize - lPositionInPage;
  594. if lAvailableInThisPage>=Count then begin
  595. move(Buffer,(PBYTE(pCache^.Buffer)+lPositionInPage)^,Count);
  596. if not pCache^.IsDirty then pCache^.IsDirty:=true;
  597. inc(FCacheStreamPosition,Count);
  598. // Update page size
  599. if lPositionInPage+Count > pCache^.PageRealSize then pCache^.PageRealSize:=lPositionInPage+Count;
  600. // Update file size
  601. if FCacheStreamPosition>FCacheStreamSize then FCacheStreamSize:=FCacheStreamPosition;
  602. Result:=Count;
  603. pCache^.LastTick:=GetOpCounter;
  604. exit;
  605. end else begin
  606. move(Buffer,(PBYTE(pCache^.Buffer)+lPositionInPage)^,lAvailableInThisPage);
  607. if not pCache^.IsDirty then pCache^.IsDirty:=true;
  608. inc(FCacheStreamPosition,lAvailableInThisPage);
  609. // Update page size
  610. if lPositionInPage+Count > pCache^.PageRealSize then pCache^.PageRealSize:=lPositionInPage+lAvailableInThisPage;
  611. // Update file size
  612. if FCacheStreamPosition>FCacheStreamSize then FCacheStreamSize:=FCacheStreamPosition;
  613. Assert(pCache^.PageRealSize=FStreamCachePageSize,'This must not happend');
  614. lNewBuffer:=PBYTE(@Buffer)+lAvailableInThisPage;
  615. Result:=lAvailableInThisPage+DoCacheWrite(lNewBuffer^,Count-lAvailableInThisPage);
  616. exit;
  617. end;
  618. end else begin
  619. // The position is in other cache page or not in cache at all, so look for
  620. // position in cached pages or allocate a new page.
  621. if LookForPositionInPages then begin
  622. Result:=DoCacheWrite(Buffer,Count);
  623. exit;
  624. end else begin
  625. if ReadPageBeforeWrite then begin
  626. Result:=DoCacheWrite(Buffer,Count);
  627. end else begin
  628. Result:=0;
  629. end;
  630. exit;
  631. end;
  632. end;
  633. end else begin
  634. if ReadPageBeforeWrite then begin
  635. Result:=DoCacheWrite(Buffer,Count);
  636. end else begin
  637. Result:=0;
  638. end;
  639. exit;
  640. end;
  641. end else begin
  642. // The page has been discarded for some unknown reason
  643. Raise EStreamError.Create(SErrCacheUnexpectedPageDiscard);
  644. end;
  645. end;
  646. function TBufferedFileStream.GetPosition: Int64;
  647. begin
  648. Result:=FCacheStreamPosition;
  649. end;
  650. procedure TBufferedFileStream.SetPosition(const Pos: Int64);
  651. begin
  652. if Pos<0 then begin
  653. FCacheStreamPosition:=0;
  654. end else begin
  655. FCacheStreamPosition:=Pos;
  656. end;
  657. end;
  658. function TBufferedFileStream.GetSize: Int64;
  659. begin
  660. Result:=FCacheStreamSize;
  661. end;
  662. procedure TBufferedFileStream.SetSize64(const NewSize: Int64);
  663. var
  664. j: integer;
  665. pCache: PStreamCacheEntry;
  666. begin
  667. WriteDirtyPages;
  668. inherited SetSize64(NewSize);
  669. FCacheStreamSize:=inherited Seek(0,soFromEnd);
  670. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  671. pCache:=FCachePages[j];
  672. if Assigned(pCache^.Buffer) and (pCache^.PageRealSize+pCache^.PageBegin>FCacheStreamSize) then begin
  673. // This page is out of bounds the new file size
  674. // so discard it.
  675. FreePage(pCache,True);
  676. break;
  677. end;
  678. end;
  679. end;
  680. procedure TBufferedFileStream.SetSize(NewSize: Longint);
  681. begin
  682. SetSize64(NewSize);
  683. end;
  684. procedure TBufferedFileStream.SetSize(const NewSize: Int64);
  685. begin
  686. SetSize64(NewSize);
  687. end;
  688. constructor TBufferedFileStream.Create(const AFileName: string; Mode: Word);
  689. begin
  690. // Initialize with 8 blocks of 4096 bytes
  691. InitializeCache(TSTREAMCACHEPAGE_SIZE_DEFAULT,TSTREAMCACHEPAGE_MAXCOUNT_DEFAULT);
  692. inherited Create(AFileName,Mode);
  693. FCacheStreamSize:=inherited Seek(int64(0),soEnd);
  694. end;
  695. constructor TBufferedFileStream.Create(const AFileName: string; Mode: Word;
  696. Rights: Cardinal);
  697. begin
  698. // Initialize with 8 blocks of 4096 bytes
  699. InitializeCache(TSTREAMCACHEPAGE_SIZE_DEFAULT,TSTREAMCACHEPAGE_MAXCOUNT_DEFAULT);
  700. inherited Create(AFileName,Mode,Rights);
  701. FCacheStreamSize:=inherited Seek(int64(0),soEnd);
  702. end;
  703. function TBufferedFileStream.Read(var Buffer; Count: Longint): Longint;
  704. begin
  705. Result:=DoCacheRead(Buffer,Count);
  706. end;
  707. function TBufferedFileStream.Write(const Buffer; Count: Longint): Longint;
  708. begin
  709. Result:=DoCacheWrite(Buffer,Count);
  710. end;
  711. procedure TBufferedFileStream.Flush;
  712. begin
  713. WriteDirtyPages;
  714. end;
  715. function TBufferedFileStream.Seek(Offset: Longint; Origin: Word): Longint;
  716. begin
  717. Result:=Seek(int64(OffSet),TSeekOrigin(Origin));
  718. end;
  719. function TBufferedFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  720. var
  721. lNewOffset: int64;
  722. begin
  723. Case Origin of
  724. soEnd:
  725. begin
  726. lNewOffset:=FCacheStreamSize+Offset;
  727. end;
  728. soBeginning:
  729. begin
  730. lNewOffset:=0+Offset;
  731. end;
  732. soCurrent:
  733. begin
  734. lNewOffset:=FCacheStreamPosition+Offset;
  735. end;
  736. end;
  737. if lNewOffset>=0 then begin
  738. FCacheStreamPosition:=lNewOffset;
  739. Result:=lNewOffset;
  740. end else begin
  741. // This is compatible with FPC stream
  742. // as it returns the negative value :-?
  743. // but in fact does not move the read pointer.
  744. Result:=-1;
  745. end;
  746. end;
  747. procedure TBufferedFileStream.InitializeCache(const aCacheBlockSize: integer;
  748. const aCacheBlockCount: integer);
  749. var
  750. j: integer;
  751. begin
  752. ClearCache;
  753. FStreamCachePageSize:=aCacheBlockSize;
  754. FStreamCachePageMaxCount:=aCacheBlockCount;
  755. FCacheStreamSize:=inherited Seek(0,soEnd);
  756. SetLength(FCachePages,FStreamCachePageMaxCount);
  757. for j := 0 to Pred(FStreamCachePageMaxCount) do begin
  758. FCachePages[j]:=New(PStreamCacheEntry);
  759. FillByte(FCachePages[j]^,Sizeof(PStreamCacheEntry^),0);
  760. end;
  761. end;
  762. destructor TBufferedFileStream.Destroy;
  763. begin
  764. ClearCache;
  765. inherited Destroy;
  766. end;
  767. end.