dbf_pgfile.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. unit dbf_pgfile;
  2. interface
  3. {$I Dbf_Common.inc}
  4. uses
  5. Classes,
  6. SysUtils,
  7. Dbf_Common;
  8. //const
  9. // MaxHeaders = 256;
  10. type
  11. EPagedFile = Exception;
  12. TPagedFileMode = (pfNone, pfMemoryCreate, pfMemoryOpen, pfExclusiveCreate,
  13. pfExclusiveOpen, pfReadWriteCreate, pfReadWriteOpen, pfReadOnly);
  14. // access levels:
  15. //
  16. // - memory create
  17. // - exclusive create/open
  18. // - read/write create/open
  19. // - readonly open
  20. //
  21. // - memory -*-share: N/A -*-locks: disabled -*-indexes: read/write
  22. // - exclusive_create -*-share: deny write -*-locks: disabled -*-indexes: read/write
  23. // - exclusive_open -*-share: deny write -*-locks: disabled -*-indexes: read/write
  24. // - readwrite_create -*-share: deny none -*-locks: enabled -*-indexes: read/write
  25. // - readwrite_open -*-share: deny none -*-locks: enabled -*-indexes: read/write
  26. // - readonly -*-share: deny none -*-locks: disabled -*-indexes: readonly
  27. TPagedFile = class(TObject)
  28. protected
  29. FStream: TStream;
  30. FHeaderOffset: Integer;
  31. FHeaderSize: Integer;
  32. FRecordSize: Integer;
  33. FPageSize: Integer; { need for MDX, where recordsize <> pagesize }
  34. FRecordCount: Integer; { actually FPageCount, but we want to keep existing code }
  35. FPagesPerRecord: Integer;
  36. FCachedSize: Integer;
  37. FCachedRecordCount: Integer;
  38. FHeader: PChar;
  39. FActive: Boolean;
  40. FNeedRecalc: Boolean;
  41. FHeaderModified: Boolean;
  42. FPageOffsetByHeader: Boolean; { do pages start after header or just at BOF? }
  43. FMode: TPagedFileMode;
  44. FTempMode: TPagedFileMode;
  45. FUserMode: TPagedFileMode;
  46. FAutoCreate: Boolean;
  47. FNeedLocks: Boolean;
  48. FVirtualLocks: Boolean;
  49. FFileLocked: Boolean;
  50. FFileName: string;
  51. FBufferPtr: Pointer;
  52. FBufferAhead: Boolean;
  53. FBufferPage: Integer;
  54. FBufferOffset: Integer;
  55. FBufferSize: Integer;
  56. FBufferReadSize: Integer;
  57. FBufferMaxSize: Integer;
  58. FBufferModified: Boolean;
  59. FWriteError: Boolean;
  60. protected
  61. procedure SetHeaderOffset(NewValue: Integer); virtual;
  62. procedure SetRecordSize(NewValue: Integer); virtual;
  63. procedure SetHeaderSize(NewValue: Integer); virtual;
  64. procedure SetPageSize(NewValue: Integer);
  65. procedure SetPageOffsetByHeader(NewValue: Boolean); virtual;
  66. procedure SetRecordCount(NewValue: Integer);
  67. procedure SetBufferAhead(NewValue: Boolean);
  68. procedure SetFileName(NewName: string);
  69. procedure SetStream(NewStream: TStream);
  70. function LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean; virtual;
  71. function UnlockSection(const Offset, Length: Cardinal): Boolean; virtual;
  72. procedure UpdateBufferSize;
  73. procedure RecalcPagesPerRecord;
  74. procedure ReadHeader;
  75. procedure FlushHeader;
  76. procedure FlushBuffer;
  77. function ReadChar: Byte;
  78. procedure WriteChar(c: Byte);
  79. procedure CheckCachedSize(const APosition: Integer);
  80. procedure SynchronizeBuffer(IntRecNum: Integer);
  81. function Read(Buffer: Pointer; ASize: Integer): Integer;
  82. function ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
  83. function SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  84. procedure WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
  85. procedure SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
  86. function GetRecordCount: Integer;
  87. procedure UpdateCachedSize(CurrPos: Integer);
  88. property VirtualLocks: Boolean read FVirtualLocks write FVirtualLocks;
  89. public
  90. constructor Create;
  91. destructor Destroy; override;
  92. procedure CloseFile; virtual;
  93. procedure OpenFile; virtual;
  94. procedure DeleteFile;
  95. procedure TryExclusive; virtual;
  96. procedure EndExclusive; virtual;
  97. procedure CheckExclusiveAccess;
  98. procedure DisableForceCreate;
  99. function CalcPageOffset(const PageNo: Integer): Integer;
  100. function IsRecordPresent(IntRecNum: Integer): boolean;
  101. function ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer; virtual;
  102. procedure WriteRecord(IntRecNum: Integer; Buffer: Pointer); virtual;
  103. procedure WriteHeader; virtual;
  104. function FileCreated: Boolean;
  105. function IsSharedAccess: Boolean;
  106. procedure ResetError;
  107. function LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
  108. function LockAllPages(const Wait: Boolean): Boolean;
  109. procedure UnlockPage(const PageNo: Integer);
  110. procedure UnlockAllPages;
  111. procedure Flush; virtual;
  112. property Active: Boolean read FActive;
  113. property AutoCreate: Boolean read FAutoCreate write FAutoCreate; // only write when closed!
  114. property Mode: TPagedFileMode read FMode write FMode; // only write when closed!
  115. property TempMode: TPagedFileMode read FTempMode;
  116. property NeedLocks: Boolean read FNeedLocks;
  117. property HeaderOffset: Integer read FHeaderOffset write SetHeaderOffset;
  118. property HeaderSize: Integer read FHeaderSize write SetHeaderSize;
  119. property RecordSize: Integer read FRecordSize write SetRecordSize;
  120. property PageSize: Integer read FPageSize write SetPageSize;
  121. property PagesPerRecord: Integer read FPagesPerRecord;
  122. property RecordCount: Integer read GetRecordCount write SetRecordCount;
  123. property CachedRecordCount: Integer read FCachedRecordCount;
  124. property PageOffsetByHeader: Boolean read FPageOffsetbyHeader write SetPageOffsetByHeader;
  125. property FileLocked: Boolean read FFileLocked;
  126. property Header: PChar read FHeader;
  127. property FileName: string read FFileName write SetFileName;
  128. property Stream: TStream read FStream write SetStream;
  129. property BufferAhead: Boolean read FBufferAhead write SetBufferAhead;
  130. property WriteError: Boolean read FWriteError;
  131. end;
  132. implementation
  133. uses
  134. {$ifdef WIN32}
  135. Windows,
  136. {$else}
  137. {$ifdef KYLIX}
  138. Libc,
  139. {$endif}
  140. Types, Dbf_Wtil,
  141. {$endif}
  142. Dbf_Str;
  143. //====================================================================
  144. // TPagedFile
  145. //====================================================================
  146. constructor TPagedFile.Create;
  147. begin
  148. FFileName := EmptyStr;
  149. FHeaderOffset := 0;
  150. FHeaderSize := 0;
  151. FRecordSize := 0;
  152. FRecordCount := 0;
  153. FPageSize := 0;
  154. FPagesPerRecord := 0;
  155. FActive := false;
  156. FHeaderModified := false;
  157. FPageOffsetByHeader := true;
  158. FNeedLocks := false;
  159. FMode := pfReadOnly;
  160. FTempMode := pfNone;
  161. FAutoCreate := false;
  162. FVirtualLocks := true;
  163. FFileLocked := false;
  164. FHeader := nil;
  165. FBufferPtr := nil;
  166. FBufferAhead := false;
  167. FBufferModified := false;
  168. FBufferSize := 0;
  169. FBufferMaxSize := 0;
  170. FBufferOffset := 0;
  171. FWriteError := false;
  172. inherited;
  173. end;
  174. destructor TPagedFile.Destroy;
  175. begin
  176. // close physical file
  177. if FFileLocked then UnlockAllPages;
  178. CloseFile;
  179. FFileLocked := false;
  180. // free mem
  181. if FHeader <> nil then
  182. FreeMem(FHeader);
  183. inherited;
  184. end;
  185. procedure TPagedFile.OpenFile;
  186. var
  187. fileOpenMode: Word;
  188. begin
  189. if FActive then exit;
  190. // store user specified mode
  191. FUserMode := FMode;
  192. if not (FMode in [pfMemoryCreate, pfMemoryOpen]) then
  193. begin
  194. // test if file exists
  195. if not FileExists(FFileName) then
  196. begin
  197. // if auto-creating, adjust mode
  198. if FAutoCreate then case FMode of
  199. pfExclusiveOpen: FMode := pfExclusiveCreate;
  200. pfReadWriteOpen, pfReadOnly: FMode := pfReadWriteCreate;
  201. end;
  202. // it seems the VCL cannot share a file that is created?
  203. // create file first, then open it in requested mode
  204. // filecreated means 'to be created' in this context ;-)
  205. if FileCreated then
  206. FileClose(FileCreate(FFileName))
  207. else
  208. raise EPagedFile.CreateFmt(STRING_FILE_NOT_FOUND,[FFileName]);
  209. end;
  210. // specify open mode
  211. case FMode of
  212. pfExclusiveCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
  213. pfExclusiveOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
  214. pfReadWriteCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
  215. pfReadWriteOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
  216. else // => readonly
  217. fileOpenMode := fmOpenRead or fmShareDenyNone;
  218. end;
  219. // open file
  220. FStream := TFileStream.Create(FFileName, fileOpenMode);
  221. // if creating, then empty file
  222. if FileCreated then
  223. FStream.Size := 0;
  224. end else begin
  225. if FStream = nil then
  226. begin
  227. FMode := pfMemoryCreate;
  228. FStream := TMemoryStream.Create;
  229. end;
  230. end;
  231. // init size var
  232. FCachedSize := Stream.Size;
  233. // update whether we need locking
  234. {$ifdef _DEBUG}
  235. FNeedLocks := true;
  236. {$else}
  237. FNeedLocks := IsSharedAccess;
  238. {$endif}
  239. FActive := true;
  240. end;
  241. procedure TPagedFile.CloseFile;
  242. begin
  243. if FActive then
  244. begin
  245. FlushHeader;
  246. // don't free the user's stream
  247. if not (FMode in [pfMemoryOpen, pfMemoryCreate]) then
  248. FreeAndNil(FStream);
  249. // mode possibly overridden in case of auto-created file
  250. FMode := FUserMode;
  251. FActive := false;
  252. FCachedRecordCount := 0;
  253. end;
  254. end;
  255. procedure TPagedFile.DeleteFile;
  256. begin
  257. // opened -> we can not delete
  258. if not FActive then
  259. SysUtils.DeleteFile(FileName);
  260. end;
  261. function TPagedFile.FileCreated: Boolean;
  262. const
  263. CreationModes: array [pfNone..pfReadOnly] of Boolean =
  264. (false, true, false, true, false, true, false, false);
  265. // node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
  266. begin
  267. Result := CreationModes[FMode];
  268. end;
  269. function TPagedFile.IsSharedAccess: Boolean;
  270. const
  271. SharedAccessModes: array [pfNone..pfReadOnly] of Boolean =
  272. (false, false, false, false, false, true, true, true);
  273. // node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
  274. begin
  275. Result := SharedAccessModes[FMode];
  276. end;
  277. procedure TPagedFile.CheckExclusiveAccess;
  278. begin
  279. // in-memory => exclusive access!
  280. if IsSharedAccess then
  281. raise EDbfError.Create(STRING_NEED_EXCLUSIVE_ACCESS);
  282. end;
  283. function TPagedFile.CalcPageOffset(const PageNo: Integer): Integer;
  284. begin
  285. if not FPageOffsetByHeader then
  286. Result := FPageSize * PageNo
  287. else if PageNo = 0 then
  288. Result := 0
  289. else
  290. Result := FHeaderOffset + FHeaderSize + (FPageSize * (PageNo - 1))
  291. end;
  292. procedure TPagedFile.CheckCachedSize(const APosition: Integer);
  293. begin
  294. // file expanded?
  295. if APosition > FCachedSize then
  296. begin
  297. FCachedSize := APosition;
  298. FNeedRecalc := true;
  299. end;
  300. end;
  301. function TPagedFile.Read(Buffer: Pointer; ASize: Integer): Integer;
  302. begin
  303. // if we cannot read due to a lock, then wait a bit
  304. repeat
  305. Result := FStream.Read(Buffer^, ASize);
  306. if Result = 0 then
  307. begin
  308. // translation to linux???
  309. if GetLastError = ERROR_LOCK_VIOLATION then
  310. begin
  311. // wait a bit until block becomes available
  312. Sleep(1);
  313. end else begin
  314. // return empty block
  315. exit;
  316. end;
  317. end else
  318. exit;
  319. until false;
  320. end;
  321. procedure TPagedFile.UpdateCachedSize(CurrPos: Integer);
  322. begin
  323. // have we added a record?
  324. if CurrPos > FCachedSize then
  325. begin
  326. // update cached size, always at end
  327. repeat
  328. Inc(FCachedSize, FRecordSize);
  329. Inc(FRecordCount, PagesPerRecord);
  330. until FCachedSize >= CurrPos;
  331. end;
  332. end;
  333. procedure TPagedFile.FlushBuffer;
  334. begin
  335. if FBufferAhead and FBufferModified then
  336. begin
  337. WriteBlock(FBufferPtr, FBufferSize, FBufferOffset);
  338. FBufferModified := false;
  339. end;
  340. end;
  341. function TPagedFile.SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  342. begin
  343. Result := ReadBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
  344. end;
  345. procedure TPagedFile.SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
  346. begin
  347. WriteBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
  348. end;
  349. procedure TPagedFile.SynchronizeBuffer(IntRecNum: Integer);
  350. begin
  351. // record outside buffer, flush previous buffer
  352. FlushBuffer;
  353. // read new set of records
  354. FBufferPage := IntRecNum;
  355. FBufferOffset := CalcPageOffset(IntRecNum);
  356. if FBufferOffset + FBufferMaxSize > FCachedSize then
  357. FBufferReadSize := FCachedSize - FBufferOffset
  358. else
  359. FBufferReadSize := FBufferMaxSize;
  360. FBufferSize := FBufferReadSize;
  361. FBufferReadSize := ReadBlock(FBufferPtr, FBufferReadSize, FBufferOffset);
  362. end;
  363. function TPagedFile.IsRecordPresent(IntRecNum: Integer): boolean;
  364. begin
  365. // if in shared mode, recordcount can only increase, check if recordno
  366. // in range for cached recordcount
  367. if not IsSharedAccess or (IntRecNum > FCachedRecordCount) then
  368. FCachedRecordCount := RecordCount;
  369. Result := (0 <= IntRecNum) and (IntRecNum <= FCachedRecordCount);
  370. end;
  371. function TPagedFile.ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  372. var
  373. Offset: Integer;
  374. begin
  375. if FBufferAhead then
  376. begin
  377. Offset := (IntRecNum - FBufferPage) * PageSize;
  378. if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
  379. (Offset+RecordSize <= FBufferReadSize) then
  380. begin
  381. // have record in buffer, nothing to do here
  382. end else begin
  383. // need to update buffer
  384. SynchronizeBuffer(IntRecNum);
  385. // check if enough bytes read
  386. if RecordSize > FBufferReadSize then
  387. begin
  388. Result := 0;
  389. exit;
  390. end;
  391. // reset offset into buffer
  392. Offset := 0;
  393. end;
  394. // now we have this record in buffer
  395. Move(PChar(FBufferPtr)[Offset], Buffer^, RecordSize);
  396. // successful
  397. Result := RecordSize;
  398. end else begin
  399. // no buffering
  400. Result := SingleReadRecord(IntRecNum, Buffer);
  401. end;
  402. end;
  403. procedure TPagedFile.WriteRecord(IntRecNum: Integer; Buffer: Pointer);
  404. var
  405. RecEnd: Integer;
  406. begin
  407. if FBufferAhead then
  408. begin
  409. RecEnd := (IntRecNum - FBufferPage + PagesPerRecord) * PageSize;
  410. if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
  411. (RecEnd <= FBufferMaxSize) then
  412. begin
  413. // extend buffer?
  414. if RecEnd > FBufferSize then
  415. FBufferSize := RecEnd;
  416. end else begin
  417. // record outside buffer, need to synchronize first
  418. SynchronizeBuffer(IntRecNum);
  419. RecEnd := PagesPerRecord * PageSize;
  420. end;
  421. // we can write this record to buffer
  422. Move(Buffer^, PChar(FBufferPtr)[RecEnd-RecordSize], RecordSize);
  423. FBufferModified := true;
  424. // update cached size
  425. UpdateCachedSize(FBufferOffset+RecEnd);
  426. end else begin
  427. // no buffering
  428. SingleWriteRecord(IntRecNum, Buffer);
  429. // update cached size
  430. UpdateCachedSize(FStream.Position);
  431. end;
  432. end;
  433. procedure TPagedFile.SetBufferAhead(NewValue: Boolean);
  434. begin
  435. if FBufferAhead <> NewValue then
  436. begin
  437. FlushBuffer;
  438. FBufferAhead := NewValue;
  439. UpdateBufferSize;
  440. end;
  441. end;
  442. procedure TPagedFile.SetStream(NewStream: TStream);
  443. begin
  444. if not FActive then
  445. FStream := NewStream;
  446. end;
  447. procedure TPagedFile.SetFileName(NewName: string);
  448. begin
  449. if not FActive then
  450. FFileName := NewName;
  451. end;
  452. procedure TPagedFile.UpdateBufferSize;
  453. begin
  454. if FBufferAhead then
  455. begin
  456. FBufferMaxSize := 65536;
  457. if RecordSize <> 0 then
  458. Dec(FBufferMaxSize, FBufferMaxSize mod PageSize);
  459. end else begin
  460. FBufferMaxSize := 0;
  461. end;
  462. if FBufferPtr <> nil then
  463. FreeMem(FBufferPtr);
  464. if FBufferAhead and (FBufferMaxSize <> 0) then
  465. GetMem(FBufferPtr, FBufferMaxSize)
  466. else
  467. FBufferPtr := nil;
  468. FBufferPage := -1;
  469. FBufferOffset := -1;
  470. FBufferModified := false;
  471. end;
  472. procedure TPagedFile.WriteHeader;
  473. begin
  474. FHeaderModified := true;
  475. if FNeedLocks then
  476. FlushHeader;
  477. end;
  478. procedure TPagedFile.FlushHeader;
  479. begin
  480. if FHeaderModified then
  481. begin
  482. FStream.Position := FHeaderOffset;
  483. FWriteError := (FStream.Write(FHeader^, FHeaderSize) = 0) or FWriteError;
  484. // test if written new header
  485. if FStream.Position > FCachedSize then
  486. begin
  487. // new header -> record count unknown
  488. FCachedSize := FStream.Position;
  489. FNeedRecalc := true;
  490. end;
  491. FHeaderModified := false;
  492. end;
  493. end;
  494. procedure TPagedFile.ReadHeader;
  495. { assumes header is large enough }
  496. var
  497. size: Integer;
  498. begin
  499. // save changes before reading new header
  500. FlushHeader;
  501. // check if header length zero
  502. if FHeaderSize <> 0 then
  503. begin
  504. // get size left in file for header
  505. size := FStream.Size - FHeaderOffset;
  506. // header start before EOF?
  507. if size >= 0 then
  508. begin
  509. // go to header start
  510. FStream.Position := FHeaderOffset;
  511. // whole header in file?
  512. if size >= FHeaderSize then
  513. begin
  514. // read header, nothing to be cleared
  515. Read(FHeader, FHeaderSize);
  516. size := FHeaderSize;
  517. end else begin
  518. // read what we can, clear rest
  519. Read(FHeader, size);
  520. end;
  521. end else begin
  522. // header start before EOF, clear header
  523. size := 0;
  524. end;
  525. FillChar(FHeader[size], FHeaderSize-size, 0);
  526. end;
  527. end;
  528. procedure TPagedFile.TryExclusive;
  529. const NewTempMode: array[pfReadWriteCreate..pfReadOnly] of TPagedFileMode =
  530. (pfReadWriteOpen, pfReadWriteOpen, pfReadOnly);
  531. begin
  532. // already in temporary exclusive mode?
  533. if (FTempMode = pfNone) and IsSharedAccess then
  534. begin
  535. // save temporary mode, if now creating, then reopen non-create
  536. FTempMode := NewTempMode[FMode];
  537. // try exclusive mode
  538. CloseFile;
  539. FMode := pfExclusiveOpen;
  540. try
  541. OpenFile;
  542. except
  543. on EFOpenError do
  544. begin
  545. // we failed, reopen normally
  546. EndExclusive;
  547. end;
  548. end;
  549. end;
  550. end;
  551. procedure TPagedFile.EndExclusive;
  552. begin
  553. // are we in temporary file mode?
  554. if FTempMode <> pfNone then
  555. begin
  556. CloseFile;
  557. FMode := FTempMode;
  558. FTempMode := pfNone;
  559. OpenFile;
  560. end;
  561. end;
  562. procedure TPagedFile.DisableForceCreate;
  563. begin
  564. case FMode of
  565. pfExclusiveCreate: FMode := pfExclusiveOpen;
  566. pfReadWriteCreate: FMode := pfReadWriteOpen;
  567. end;
  568. end;
  569. procedure TPagedFile.SetHeaderOffset(NewValue: Integer);
  570. //
  571. // *) assumes is called right before SetHeaderSize
  572. //
  573. begin
  574. if FHeaderOffset <> NewValue then
  575. begin
  576. FlushHeader;
  577. FHeaderOffset := NewValue;
  578. end;
  579. end;
  580. procedure TPagedFile.SetHeaderSize(NewValue: Integer);
  581. begin
  582. if FHeaderSize <> NewValue then
  583. begin
  584. FlushHeader;
  585. if (FHeader <> nil) and (NewValue <> 0) then
  586. FreeMem(FHeader);
  587. FHeaderSize := NewValue;
  588. if FHeaderSize <> 0 then
  589. GetMem(FHeader, FHeaderSize);
  590. FNeedRecalc := true;
  591. ReadHeader;
  592. end;
  593. end;
  594. procedure TPagedFile.SetRecordSize(NewValue: Integer);
  595. begin
  596. if FRecordSize <> NewValue then
  597. begin
  598. FRecordSize := NewValue;
  599. FPageSize := NewValue;
  600. FNeedRecalc := true;
  601. RecalcPagesPerRecord;
  602. end;
  603. end;
  604. procedure TPagedFile.SetPageSize(NewValue: Integer);
  605. begin
  606. if FPageSize <> NewValue then
  607. begin
  608. FPageSize := NewValue;
  609. FNeedRecalc := true;
  610. RecalcPagesPerRecord;
  611. UpdateBufferSize;
  612. end;
  613. end;
  614. procedure TPagedFile.RecalcPagesPerRecord;
  615. begin
  616. if FPageSize = 0 then
  617. FPagesPerRecord := 0
  618. else
  619. FPagesPerRecord := FRecordSize div FPageSize;
  620. end;
  621. function TPagedFile.GetRecordCount: Integer;
  622. var
  623. currSize: Integer;
  624. begin
  625. // file size changed?
  626. if FNeedLocks then
  627. begin
  628. currSize := FStream.Size;
  629. if currSize <> FCachedSize then
  630. begin
  631. FCachedSize := currSize;
  632. FNeedRecalc := true;
  633. end;
  634. end;
  635. // try to optimize speed
  636. if FNeedRecalc then
  637. begin
  638. // no file? test flags
  639. if (FPageSize = 0) or not FActive then
  640. FRecordCount := 0
  641. else
  642. if FPageOffsetByHeader then
  643. FRecordCount := (FCachedSize - FHeaderSize - FHeaderOffset) div FPageSize
  644. else
  645. FRecordCount := FCachedSize div FPageSize;
  646. if FRecordCount < 0 then
  647. FRecordCount := 0;
  648. // count updated
  649. FNeedRecalc := false;
  650. end;
  651. Result := FRecordCount;
  652. end;
  653. procedure TPagedFile.SetRecordCount(NewValue: Integer);
  654. begin
  655. if RecordCount <> NewValue then
  656. begin
  657. if FPageOffsetByHeader then
  658. FCachedSize := FHeaderSize + FHeaderOffset + FPageSize * NewValue
  659. else
  660. FCachedSize := FPageSize * NewValue;
  661. // FCachedSize := CalcPageOffset(NewValue);
  662. FRecordCount := NewValue;
  663. FStream.Size := FCachedSize;
  664. end;
  665. end;
  666. procedure TPagedFile.SetPageOffsetByHeader(NewValue: Boolean);
  667. begin
  668. if FPageOffsetByHeader <> NewValue then
  669. begin
  670. FPageOffsetByHeader := NewValue;
  671. FNeedRecalc := true;
  672. end;
  673. end;
  674. procedure TPagedFile.WriteChar(c: Byte);
  675. begin
  676. FWriteError := (FStream.Write(c, 1) = 0) or FWriteError;
  677. end;
  678. function TPagedFile.ReadChar: Byte;
  679. begin
  680. Read(@Result, 1);
  681. end;
  682. procedure TPagedFile.Flush;
  683. begin
  684. end;
  685. function TPagedFile.ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
  686. begin
  687. FStream.Position := APosition;
  688. CheckCachedSize(APosition);
  689. Result := Read(BlockPtr, ASize);
  690. end;
  691. procedure TPagedFile.WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
  692. // assumes a lock is held if necessary prior to calling this function
  693. begin
  694. FStream.Position := APosition;
  695. CheckCachedSize(APosition);
  696. FWriteError := (FStream.Write(BlockPtr^, ASize) = 0) or FWriteError;
  697. end;
  698. procedure TPagedFile.ResetError;
  699. begin
  700. FWriteError := false;
  701. end;
  702. // BDE compatible lock offset found!
  703. const
  704. {$ifdef WIN32}
  705. LockOffset = $EFFFFFFE; // BDE compatible
  706. FileLockSize = 2;
  707. {$else}
  708. LockOffset = $7FFFFFFF;
  709. FileLockSize = 1;
  710. {$endif}
  711. // dBase supports maximum of a billion records
  712. LockStart = LockOffset - 1000000000;
  713. function TPagedFile.LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean;
  714. // assumes FNeedLock = true
  715. var
  716. Failed: Boolean;
  717. begin
  718. // FNeedLocks => FStream is of type TFileStream
  719. Failed := false;
  720. repeat
  721. Result := LockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
  722. // test if lock violation, then wait a bit and try again
  723. if not Result and Wait then
  724. begin
  725. if (GetLastError = ERROR_LOCK_VIOLATION) then
  726. Sleep(10)
  727. else
  728. Failed := true;
  729. end;
  730. until Result or not Wait or Failed;
  731. end;
  732. function TPagedFile.UnlockSection(const Offset, Length: Cardinal): Boolean;
  733. begin
  734. Result := UnlockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
  735. end;
  736. function TPagedFile.LockAllPages(const Wait: Boolean): Boolean;
  737. var
  738. Offset: Cardinal;
  739. Length: Cardinal;
  740. begin
  741. // do we need locking?
  742. if FNeedLocks and not FFileLocked then
  743. begin
  744. if FVirtualLocks then
  745. begin
  746. {$ifdef SUPPORT_UINT32_CARDINAL}
  747. Offset := LockStart;
  748. Length := LockOffset - LockStart + FileLockSize;
  749. {$else}
  750. // delphi 3 has strange types:
  751. // cardinal 0..2 GIG ?? does it produce correct code?
  752. Offset := Cardinal(LockStart);
  753. Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
  754. {$endif}
  755. end else begin
  756. Offset := 0;
  757. Length := $7FFFFFFF;
  758. end;
  759. // lock requested section
  760. Result := LockSection(Offset, Length, Wait);
  761. FFileLocked := Result;
  762. end else
  763. Result := true;
  764. end;
  765. procedure TPagedFile.UnlockAllPages;
  766. var
  767. Offset: Cardinal;
  768. Length: Cardinal;
  769. begin
  770. // do we need locking?
  771. if FNeedLocks and FFileLocked then
  772. begin
  773. if FVirtualLocks then
  774. begin
  775. {$ifdef SUPPORT_UINT32_CARDINAL}
  776. Offset := LockStart;
  777. Length := LockOffset - LockStart + FileLockSize;
  778. {$else}
  779. // delphi 3 has strange types:
  780. // cardinal 0..2 GIG ?? does it produce correct code?
  781. Offset := Cardinal(LockStart);
  782. Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
  783. {$endif}
  784. end else begin
  785. Offset := 0;
  786. Length := $7FFFFFFF;
  787. end;
  788. // unlock requested section
  789. // FNeedLocks => FStream is of type TFileStream
  790. FFileLocked := not UnlockSection(Offset, Length);
  791. end;
  792. end;
  793. function TPagedFile.LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
  794. var
  795. Offset: Cardinal;
  796. Length: Cardinal;
  797. begin
  798. // do we need locking?
  799. if FNeedLocks and not FFileLocked then
  800. begin
  801. if FVirtualLocks then
  802. begin
  803. Offset := LockOffset - Cardinal(PageNo);
  804. Length := 1;
  805. end else begin
  806. Offset := CalcPageOffset(PageNo);
  807. Length := RecordSize;
  808. end;
  809. // lock requested section
  810. Result := LockSection(Offset, Length, Wait);
  811. end else
  812. Result := true;
  813. end;
  814. procedure TPagedFile.UnlockPage(const PageNo: Integer);
  815. var
  816. Offset: Cardinal;
  817. Length: Cardinal;
  818. begin
  819. // do we need locking?
  820. if FNeedLocks and not FFileLocked then
  821. begin
  822. // calc offset + length
  823. if FVirtualLocks then
  824. begin
  825. Offset := LockOffset - Cardinal(PageNo);
  826. Length := 1;
  827. end else begin
  828. Offset := CalcPageOffset(PageNo);
  829. Length := RecordSize;
  830. end;
  831. // unlock requested section
  832. // FNeedLocks => FStream is of type TFileStream
  833. UnlockSection(Offset, Length);
  834. end;
  835. end;
  836. end.