dbf_pgfile.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  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. // allocate memory for bufferahead
  241. UpdateBufferSize;
  242. end;
  243. procedure TPagedFile.CloseFile;
  244. begin
  245. if FActive then
  246. begin
  247. FlushHeader;
  248. FlushBuffer;
  249. // don't free the user's stream
  250. if not (FMode in [pfMemoryOpen, pfMemoryCreate]) then
  251. FreeAndNil(FStream);
  252. // free bufferahead buffer
  253. FreeMemAndNil(FBufferPtr);
  254. // mode possibly overridden in case of auto-created file
  255. FMode := FUserMode;
  256. FActive := false;
  257. FCachedRecordCount := 0;
  258. end;
  259. end;
  260. procedure TPagedFile.DeleteFile;
  261. begin
  262. // opened -> we can not delete
  263. if not FActive then
  264. SysUtils.DeleteFile(FileName);
  265. end;
  266. function TPagedFile.FileCreated: Boolean;
  267. const
  268. CreationModes: array [pfNone..pfReadOnly] of Boolean =
  269. (false, true, false, true, false, true, false, false);
  270. // node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
  271. begin
  272. Result := CreationModes[FMode];
  273. end;
  274. function TPagedFile.IsSharedAccess: Boolean;
  275. const
  276. SharedAccessModes: array [pfNone..pfReadOnly] of Boolean =
  277. (false, false, false, false, false, true, true, true);
  278. // node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
  279. begin
  280. Result := SharedAccessModes[FMode];
  281. end;
  282. procedure TPagedFile.CheckExclusiveAccess;
  283. begin
  284. // in-memory => exclusive access!
  285. if IsSharedAccess then
  286. raise EDbfError.Create(STRING_NEED_EXCLUSIVE_ACCESS);
  287. end;
  288. function TPagedFile.CalcPageOffset(const PageNo: Integer): Integer;
  289. begin
  290. if not FPageOffsetByHeader then
  291. Result := FPageSize * PageNo
  292. else if PageNo = 0 then
  293. Result := 0
  294. else
  295. Result := FHeaderOffset + FHeaderSize + (FPageSize * (PageNo - 1))
  296. end;
  297. procedure TPagedFile.CheckCachedSize(const APosition: Integer);
  298. begin
  299. // file expanded?
  300. if APosition > FCachedSize then
  301. begin
  302. FCachedSize := APosition;
  303. FNeedRecalc := true;
  304. end;
  305. end;
  306. function TPagedFile.Read(Buffer: Pointer; ASize: Integer): Integer;
  307. begin
  308. // if we cannot read due to a lock, then wait a bit
  309. repeat
  310. Result := FStream.Read(Buffer^, ASize);
  311. if Result = 0 then
  312. begin
  313. // translation to linux???
  314. if GetLastError = ERROR_LOCK_VIOLATION then
  315. begin
  316. // wait a bit until block becomes available
  317. Sleep(1);
  318. end else begin
  319. // return empty block
  320. exit;
  321. end;
  322. end else
  323. exit;
  324. until false;
  325. end;
  326. procedure TPagedFile.UpdateCachedSize(CurrPos: Integer);
  327. begin
  328. // have we added a record?
  329. if CurrPos > FCachedSize then
  330. begin
  331. // update cached size, always at end
  332. repeat
  333. Inc(FCachedSize, FRecordSize);
  334. Inc(FRecordCount, PagesPerRecord);
  335. until FCachedSize >= CurrPos;
  336. end;
  337. end;
  338. procedure TPagedFile.FlushBuffer;
  339. begin
  340. if FBufferAhead and FBufferModified then
  341. begin
  342. WriteBlock(FBufferPtr, FBufferSize, FBufferOffset);
  343. FBufferModified := false;
  344. end;
  345. end;
  346. function TPagedFile.SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  347. begin
  348. Result := ReadBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
  349. end;
  350. procedure TPagedFile.SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
  351. begin
  352. WriteBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
  353. end;
  354. procedure TPagedFile.SynchronizeBuffer(IntRecNum: Integer);
  355. begin
  356. // record outside buffer, flush previous buffer
  357. FlushBuffer;
  358. // read new set of records
  359. FBufferPage := IntRecNum;
  360. FBufferOffset := CalcPageOffset(IntRecNum);
  361. if FBufferOffset + FBufferMaxSize > FCachedSize then
  362. FBufferReadSize := FCachedSize - FBufferOffset
  363. else
  364. FBufferReadSize := FBufferMaxSize;
  365. FBufferSize := FBufferReadSize;
  366. FBufferReadSize := ReadBlock(FBufferPtr, FBufferReadSize, FBufferOffset);
  367. end;
  368. function TPagedFile.IsRecordPresent(IntRecNum: Integer): boolean;
  369. begin
  370. // if in shared mode, recordcount can only increase, check if recordno
  371. // in range for cached recordcount
  372. if not IsSharedAccess or (IntRecNum > FCachedRecordCount) then
  373. FCachedRecordCount := RecordCount;
  374. Result := (0 <= IntRecNum) and (IntRecNum <= FCachedRecordCount);
  375. end;
  376. function TPagedFile.ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  377. var
  378. Offset: Integer;
  379. begin
  380. if FBufferAhead then
  381. begin
  382. Offset := (IntRecNum - FBufferPage) * PageSize;
  383. if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
  384. (Offset+RecordSize <= FBufferReadSize) then
  385. begin
  386. // have record in buffer, nothing to do here
  387. end else begin
  388. // need to update buffer
  389. SynchronizeBuffer(IntRecNum);
  390. // check if enough bytes read
  391. if RecordSize > FBufferReadSize then
  392. begin
  393. Result := 0;
  394. exit;
  395. end;
  396. // reset offset into buffer
  397. Offset := 0;
  398. end;
  399. // now we have this record in buffer
  400. Move(PChar(FBufferPtr)[Offset], Buffer^, RecordSize);
  401. // successful
  402. Result := RecordSize;
  403. end else begin
  404. // no buffering
  405. Result := SingleReadRecord(IntRecNum, Buffer);
  406. end;
  407. end;
  408. procedure TPagedFile.WriteRecord(IntRecNum: Integer; Buffer: Pointer);
  409. var
  410. RecEnd: Integer;
  411. begin
  412. if FBufferAhead then
  413. begin
  414. RecEnd := (IntRecNum - FBufferPage + PagesPerRecord) * PageSize;
  415. if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
  416. (RecEnd <= FBufferMaxSize) then
  417. begin
  418. // extend buffer?
  419. if RecEnd > FBufferSize then
  420. FBufferSize := RecEnd;
  421. end else begin
  422. // record outside buffer, need to synchronize first
  423. SynchronizeBuffer(IntRecNum);
  424. RecEnd := PagesPerRecord * PageSize;
  425. end;
  426. // we can write this record to buffer
  427. Move(Buffer^, PChar(FBufferPtr)[RecEnd-RecordSize], RecordSize);
  428. FBufferModified := true;
  429. // update cached size
  430. UpdateCachedSize(FBufferOffset+RecEnd);
  431. end else begin
  432. // no buffering
  433. SingleWriteRecord(IntRecNum, Buffer);
  434. // update cached size
  435. UpdateCachedSize(FStream.Position);
  436. end;
  437. end;
  438. procedure TPagedFile.SetBufferAhead(NewValue: Boolean);
  439. begin
  440. if FBufferAhead <> NewValue then
  441. begin
  442. FlushBuffer;
  443. FBufferAhead := NewValue;
  444. UpdateBufferSize;
  445. end;
  446. end;
  447. procedure TPagedFile.SetStream(NewStream: TStream);
  448. begin
  449. if not FActive then
  450. FStream := NewStream;
  451. end;
  452. procedure TPagedFile.SetFileName(NewName: string);
  453. begin
  454. if not FActive then
  455. FFileName := NewName;
  456. end;
  457. procedure TPagedFile.UpdateBufferSize;
  458. begin
  459. if FBufferAhead then
  460. begin
  461. FBufferMaxSize := 65536;
  462. if RecordSize <> 0 then
  463. Dec(FBufferMaxSize, FBufferMaxSize mod PageSize);
  464. end else begin
  465. FBufferMaxSize := 0;
  466. end;
  467. if FBufferPtr <> nil then
  468. FreeMem(FBufferPtr);
  469. if FBufferAhead and (FBufferMaxSize <> 0) then
  470. GetMem(FBufferPtr, FBufferMaxSize)
  471. else
  472. FBufferPtr := nil;
  473. FBufferPage := -1;
  474. FBufferOffset := -1;
  475. FBufferModified := false;
  476. end;
  477. procedure TPagedFile.WriteHeader;
  478. begin
  479. FHeaderModified := true;
  480. if FNeedLocks then
  481. FlushHeader;
  482. end;
  483. procedure TPagedFile.FlushHeader;
  484. begin
  485. if FHeaderModified then
  486. begin
  487. FStream.Position := FHeaderOffset;
  488. FWriteError := (FStream.Write(FHeader^, FHeaderSize) = 0) or FWriteError;
  489. // test if written new header
  490. if FStream.Position > FCachedSize then
  491. begin
  492. // new header -> record count unknown
  493. FCachedSize := FStream.Position;
  494. FNeedRecalc := true;
  495. end;
  496. FHeaderModified := false;
  497. end;
  498. end;
  499. procedure TPagedFile.ReadHeader;
  500. { assumes header is large enough }
  501. var
  502. size: Integer;
  503. begin
  504. // save changes before reading new header
  505. FlushHeader;
  506. // check if header length zero
  507. if FHeaderSize <> 0 then
  508. begin
  509. // get size left in file for header
  510. size := FStream.Size - FHeaderOffset;
  511. // header start before EOF?
  512. if size >= 0 then
  513. begin
  514. // go to header start
  515. FStream.Position := FHeaderOffset;
  516. // whole header in file?
  517. if size >= FHeaderSize then
  518. begin
  519. // read header, nothing to be cleared
  520. Read(FHeader, FHeaderSize);
  521. size := FHeaderSize;
  522. end else begin
  523. // read what we can, clear rest
  524. Read(FHeader, size);
  525. end;
  526. end else begin
  527. // header start before EOF, clear header
  528. size := 0;
  529. end;
  530. FillChar(FHeader[size], FHeaderSize-size, 0);
  531. end;
  532. end;
  533. procedure TPagedFile.TryExclusive;
  534. const NewTempMode: array[pfReadWriteCreate..pfReadOnly] of TPagedFileMode =
  535. (pfReadWriteOpen, pfReadWriteOpen, pfReadOnly);
  536. begin
  537. // already in temporary exclusive mode?
  538. if (FTempMode = pfNone) and IsSharedAccess then
  539. begin
  540. // save temporary mode, if now creating, then reopen non-create
  541. FTempMode := NewTempMode[FMode];
  542. // try exclusive mode
  543. CloseFile;
  544. FMode := pfExclusiveOpen;
  545. try
  546. OpenFile;
  547. except
  548. on EFOpenError do
  549. begin
  550. // we failed, reopen normally
  551. EndExclusive;
  552. end;
  553. end;
  554. end;
  555. end;
  556. procedure TPagedFile.EndExclusive;
  557. begin
  558. // are we in temporary file mode?
  559. if FTempMode <> pfNone then
  560. begin
  561. CloseFile;
  562. FMode := FTempMode;
  563. FTempMode := pfNone;
  564. OpenFile;
  565. end;
  566. end;
  567. procedure TPagedFile.DisableForceCreate;
  568. begin
  569. case FMode of
  570. pfExclusiveCreate: FMode := pfExclusiveOpen;
  571. pfReadWriteCreate: FMode := pfReadWriteOpen;
  572. end;
  573. end;
  574. procedure TPagedFile.SetHeaderOffset(NewValue: Integer);
  575. //
  576. // *) assumes is called right before SetHeaderSize
  577. //
  578. begin
  579. if FHeaderOffset <> NewValue then
  580. begin
  581. FlushHeader;
  582. FHeaderOffset := NewValue;
  583. end;
  584. end;
  585. procedure TPagedFile.SetHeaderSize(NewValue: Integer);
  586. begin
  587. if FHeaderSize <> NewValue then
  588. begin
  589. FlushHeader;
  590. if (FHeader <> nil) and (NewValue <> 0) then
  591. FreeMem(FHeader);
  592. FHeaderSize := NewValue;
  593. if FHeaderSize <> 0 then
  594. GetMem(FHeader, FHeaderSize);
  595. FNeedRecalc := true;
  596. ReadHeader;
  597. end;
  598. end;
  599. procedure TPagedFile.SetRecordSize(NewValue: Integer);
  600. begin
  601. if FRecordSize <> NewValue then
  602. begin
  603. FRecordSize := NewValue;
  604. FPageSize := NewValue;
  605. FNeedRecalc := true;
  606. RecalcPagesPerRecord;
  607. end;
  608. end;
  609. procedure TPagedFile.SetPageSize(NewValue: Integer);
  610. begin
  611. if FPageSize <> NewValue then
  612. begin
  613. FPageSize := NewValue;
  614. FNeedRecalc := true;
  615. RecalcPagesPerRecord;
  616. UpdateBufferSize;
  617. end;
  618. end;
  619. procedure TPagedFile.RecalcPagesPerRecord;
  620. begin
  621. if FPageSize = 0 then
  622. FPagesPerRecord := 0
  623. else
  624. FPagesPerRecord := FRecordSize div FPageSize;
  625. end;
  626. function TPagedFile.GetRecordCount: Integer;
  627. var
  628. currSize: Integer;
  629. begin
  630. // file size changed?
  631. if FNeedLocks then
  632. begin
  633. currSize := FStream.Size;
  634. if currSize <> FCachedSize then
  635. begin
  636. FCachedSize := currSize;
  637. FNeedRecalc := true;
  638. end;
  639. end;
  640. // try to optimize speed
  641. if FNeedRecalc then
  642. begin
  643. // no file? test flags
  644. if (FPageSize = 0) or not FActive then
  645. FRecordCount := 0
  646. else
  647. if FPageOffsetByHeader then
  648. FRecordCount := (FCachedSize - FHeaderSize - FHeaderOffset) div FPageSize
  649. else
  650. FRecordCount := FCachedSize div FPageSize;
  651. if FRecordCount < 0 then
  652. FRecordCount := 0;
  653. // count updated
  654. FNeedRecalc := false;
  655. end;
  656. Result := FRecordCount;
  657. end;
  658. procedure TPagedFile.SetRecordCount(NewValue: Integer);
  659. begin
  660. if RecordCount <> NewValue then
  661. begin
  662. if FPageOffsetByHeader then
  663. FCachedSize := FHeaderSize + FHeaderOffset + FPageSize * NewValue
  664. else
  665. FCachedSize := FPageSize * NewValue;
  666. // FCachedSize := CalcPageOffset(NewValue);
  667. FRecordCount := NewValue;
  668. FStream.Size := FCachedSize;
  669. end;
  670. end;
  671. procedure TPagedFile.SetPageOffsetByHeader(NewValue: Boolean);
  672. begin
  673. if FPageOffsetByHeader <> NewValue then
  674. begin
  675. FPageOffsetByHeader := NewValue;
  676. FNeedRecalc := true;
  677. end;
  678. end;
  679. procedure TPagedFile.WriteChar(c: Byte);
  680. begin
  681. FWriteError := (FStream.Write(c, 1) = 0) or FWriteError;
  682. end;
  683. function TPagedFile.ReadChar: Byte;
  684. begin
  685. Read(@Result, 1);
  686. end;
  687. procedure TPagedFile.Flush;
  688. begin
  689. end;
  690. function TPagedFile.ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
  691. begin
  692. FStream.Position := APosition;
  693. CheckCachedSize(APosition);
  694. Result := Read(BlockPtr, ASize);
  695. end;
  696. procedure TPagedFile.WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
  697. // assumes a lock is held if necessary prior to calling this function
  698. begin
  699. FStream.Position := APosition;
  700. CheckCachedSize(APosition);
  701. FWriteError := (FStream.Write(BlockPtr^, ASize) = 0) or FWriteError;
  702. end;
  703. procedure TPagedFile.ResetError;
  704. begin
  705. FWriteError := false;
  706. end;
  707. // BDE compatible lock offset found!
  708. const
  709. {$ifdef WIN32}
  710. LockOffset = $EFFFFFFE; // BDE compatible
  711. FileLockSize = 2;
  712. {$else}
  713. LockOffset = $7FFFFFFF;
  714. FileLockSize = 1;
  715. {$endif}
  716. // dBase supports maximum of a billion records
  717. LockStart = LockOffset - 1000000000;
  718. function TPagedFile.LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean;
  719. // assumes FNeedLock = true
  720. var
  721. Failed: Boolean;
  722. begin
  723. // FNeedLocks => FStream is of type TFileStream
  724. Failed := false;
  725. repeat
  726. Result := LockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
  727. // test if lock violation, then wait a bit and try again
  728. if not Result and Wait then
  729. begin
  730. if (GetLastError = ERROR_LOCK_VIOLATION) then
  731. Sleep(10)
  732. else
  733. Failed := true;
  734. end;
  735. until Result or not Wait or Failed;
  736. end;
  737. function TPagedFile.UnlockSection(const Offset, Length: Cardinal): Boolean;
  738. begin
  739. Result := UnlockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
  740. end;
  741. function TPagedFile.LockAllPages(const Wait: Boolean): Boolean;
  742. var
  743. Offset: Cardinal;
  744. Length: Cardinal;
  745. begin
  746. // do we need locking?
  747. if FNeedLocks and not FFileLocked then
  748. begin
  749. if FVirtualLocks then
  750. begin
  751. {$ifdef SUPPORT_UINT32_CARDINAL}
  752. Offset := LockStart;
  753. Length := LockOffset - LockStart + FileLockSize;
  754. {$else}
  755. // delphi 3 has strange types:
  756. // cardinal 0..2 GIG ?? does it produce correct code?
  757. Offset := Cardinal(LockStart);
  758. Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
  759. {$endif}
  760. end else begin
  761. Offset := 0;
  762. Length := $7FFFFFFF;
  763. end;
  764. // lock requested section
  765. Result := LockSection(Offset, Length, Wait);
  766. FFileLocked := Result;
  767. end else
  768. Result := true;
  769. end;
  770. procedure TPagedFile.UnlockAllPages;
  771. var
  772. Offset: Cardinal;
  773. Length: Cardinal;
  774. begin
  775. // do we need locking?
  776. if FNeedLocks and FFileLocked then
  777. begin
  778. if FVirtualLocks then
  779. begin
  780. {$ifdef SUPPORT_UINT32_CARDINAL}
  781. Offset := LockStart;
  782. Length := LockOffset - LockStart + FileLockSize;
  783. {$else}
  784. // delphi 3 has strange types:
  785. // cardinal 0..2 GIG ?? does it produce correct code?
  786. Offset := Cardinal(LockStart);
  787. Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
  788. {$endif}
  789. end else begin
  790. Offset := 0;
  791. Length := $7FFFFFFF;
  792. end;
  793. // unlock requested section
  794. // FNeedLocks => FStream is of type TFileStream
  795. FFileLocked := not UnlockSection(Offset, Length);
  796. end;
  797. end;
  798. function TPagedFile.LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
  799. var
  800. Offset: Cardinal;
  801. Length: Cardinal;
  802. begin
  803. // do we need locking?
  804. if FNeedLocks and not FFileLocked then
  805. begin
  806. if FVirtualLocks then
  807. begin
  808. Offset := LockOffset - Cardinal(PageNo);
  809. Length := 1;
  810. end else begin
  811. Offset := CalcPageOffset(PageNo);
  812. Length := RecordSize;
  813. end;
  814. // lock requested section
  815. Result := LockSection(Offset, Length, Wait);
  816. end else
  817. Result := true;
  818. end;
  819. procedure TPagedFile.UnlockPage(const PageNo: Integer);
  820. var
  821. Offset: Cardinal;
  822. Length: Cardinal;
  823. begin
  824. // do we need locking?
  825. if FNeedLocks and not FFileLocked then
  826. begin
  827. // calc offset + length
  828. if FVirtualLocks then
  829. begin
  830. Offset := LockOffset - Cardinal(PageNo);
  831. Length := 1;
  832. end else begin
  833. Offset := CalcPageOffset(PageNo);
  834. Length := RecordSize;
  835. end;
  836. // unlock requested section
  837. // FNeedLocks => FStream is of type TFileStream
  838. UnlockSection(Offset, Length);
  839. end;
  840. end;
  841. end.