dbf_memo.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. unit dbf_memo;
  2. {
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 1999-2022 by Pascal Ganaye,Micha Nelissen and other members of the
  5. Free Pascal development team
  6. DBF memo support
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. interface
  14. {$I dbf_common.inc}
  15. uses
  16. Classes,
  17. dbf_pgfile,
  18. dbf_common;
  19. type
  20. //====================================================================
  21. { TMemoFile }
  22. TMemoFile = class(TPagedFile)
  23. private
  24. procedure SetDBFVersion(AValue: TXBaseVersion);
  25. protected
  26. FDbfFile: pointer;
  27. FDbfVersion: TXBaseVersion;
  28. FEmptySpaceFiller: AnsiChar; //filler for unused header and memo data
  29. FMemoRecordSize: Integer;
  30. FOpened: Boolean;
  31. FBuffer: PAnsiChar;
  32. protected
  33. function GetBlockLen: Integer; virtual; abstract;
  34. function GetMemoSize: Integer; virtual; abstract;
  35. function GetNextFreeBlock: Integer; virtual; abstract;
  36. procedure SetNextFreeBlock(BlockNo: Integer); virtual; abstract;
  37. procedure SetBlockLen(BlockLen: Integer); virtual; abstract;
  38. public
  39. constructor Create(ADbfFile: pointer);
  40. destructor Destroy; override;
  41. procedure Open;
  42. procedure Close;
  43. procedure ReadMemo(BlockNo: Integer; DestStream: TStream);
  44. procedure WriteMemo(var BlockNo: Integer; ReadSize: Integer; Src: TStream);
  45. property DbfVersion: TXBaseVersion read FDbfVersion write SetDBFVersion;
  46. property MemoRecordSize: Integer read FMemoRecordSize write FMemoRecordSize;
  47. end;
  48. { TFoxProMemoFile }
  49. // (Visual) Foxpro memo file support
  50. TFoxProMemoFile = class(TMemoFile)
  51. protected
  52. function GetBlockLen: Integer; override;
  53. function GetMemoSize: Integer; override;
  54. function GetNextFreeBlock: Integer; override;
  55. procedure SetNextFreeBlock(BlockNo: Integer); override;
  56. procedure SetBlockLen(BlockLen: Integer); override;
  57. end;
  58. // DBaseIII+ memo file support:
  59. TDbaseMemoFile = class(TMemoFile)
  60. protected
  61. function GetBlockLen: Integer; override;
  62. function GetMemoSize: Integer; override;
  63. function GetNextFreeBlock: Integer; override;
  64. procedure SetNextFreeBlock(BlockNo: Integer); override;
  65. procedure SetBlockLen(BlockLen: Integer); override;
  66. end;
  67. { TNullMemoFile, a kind of /dev/null memofile ;-) }
  68. { - inv: FHeaderModified == false!! (otherwise will try to write FStream) }
  69. { - inv: FHeaderSize == 0 }
  70. { - inv: FNeedLocks == false }
  71. { - WriteTo must NOT be used }
  72. { - WriteChar must NOT be used }
  73. TNullMemoFile = class(TMemoFile)
  74. protected
  75. procedure SetHeaderOffset(NewValue: Integer); override;
  76. procedure SetRecordSize(NewValue: Integer); override;
  77. procedure SetHeaderSize(NewValue: Integer); override;
  78. function LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean; override;
  79. function UnlockSection(const Offset, Length: Cardinal): Boolean; override;
  80. function GetBlockLen: Integer; override;
  81. function GetMemoSize: Integer; override;
  82. function GetNextFreeBlock: Integer; override;
  83. procedure SetNextFreeBlock(BlockNo: Integer); override;
  84. procedure SetBlockLen(BlockLen: Integer); override;
  85. public
  86. constructor Create(ADbfFile: pointer);
  87. procedure CloseFile; override;
  88. procedure OpenFile; override;
  89. function ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer; override;
  90. procedure WriteRecord(IntRecNum: Integer; Buffer: Pointer); override;
  91. end;
  92. PInteger = ^Integer;
  93. TMemoFileClass = class of TMemoFile;
  94. implementation
  95. uses
  96. SysUtils, dbf_dbffile;
  97. //====================================================================
  98. //=== Memo and binary fields support
  99. //====================================================================
  100. type
  101. // DBase III+ dbt memo file
  102. // (Visual) FoxPro note: integers are in Big Endian: high byte first
  103. // http://msdn.microsoft.com/en-us/library/aa975374%28VS.71%29.aspx
  104. PDbtHdr = ^rDbtHdr;
  105. rDbtHdr = record
  106. NextBlock : dword; // 0..3
  107. // Dummy in DBaseIII; size of blocks in memo file; default 512 bytes
  108. // (Visual) FoxPro: 4..5 unused; use only bytes 6..7
  109. BlockSize : dword; // 4..7
  110. // DBF file name without extension
  111. DbfFile : array [0..7] of Byte; // 8..15
  112. // DBase III only: version number $03
  113. bVer : Byte; // 16
  114. Dummy2 : array [17..19] of Byte; // 17..19
  115. // Block length in bytes; DBaseIII: always $01
  116. BlockLen : Word; // 20..21
  117. Dummy3 : array [22..511] of Byte;// 22..511 First block; garbage contents
  118. end;
  119. PFptHdr = ^rFptHdr;
  120. rFptHdr = record
  121. NextBlock : dword;
  122. Dummy : array [4..5] of Byte;
  123. BlockLen : Word; // 20..21
  124. Dummy3 : array [8..511] of Byte;
  125. end;
  126. // Header of a memo data block:
  127. // (Visual) FoxPro note: integers are in Big Endian: high byte first
  128. PBlockHdr = ^rBlockHdr;
  129. rBlockHdr = record
  130. // DBase IV(+) identifier: $FF $FF $08 $00
  131. // (Visual) FoxPro: $00 picture, $01 text/memo, $02 object
  132. MemoType : Cardinal; // 0..3
  133. // Length of memo field
  134. MemoSize : Cardinal; // 4..7
  135. // memo data 8..N
  136. end;
  137. procedure TMemoFile.SetDBFVersion(AValue: TXBaseVersion);
  138. begin
  139. if FDbfVersion=AValue then Exit;
  140. FDbfVersion:=AValue;
  141. if AValue in [xFoxPro, xVisualFoxPro] then
  142. // Visual Foxpro writes 0s itself, so mimic it
  143. FEmptySpaceFiller:=#0
  144. else
  145. FEmptySpaceFiller:=' ';
  146. end;
  147. //==========================================================
  148. //============ Dbtfile
  149. //==========================================================
  150. constructor TMemoFile.Create(ADbfFile: pointer);
  151. begin
  152. // init vars
  153. FBuffer := nil;
  154. FOpened := false;
  155. FEmptySpaceFiller:=' '; //default
  156. // call inherited
  157. inherited Create;
  158. FDbfFile := ADbfFile;
  159. FTempMode := TDbfFile(ADbfFile).TempMode;
  160. end;
  161. destructor TMemoFile.Destroy;
  162. begin
  163. // close file
  164. Close;
  165. // call ancestor
  166. inherited;
  167. end;
  168. procedure TMemoFile.Open;
  169. begin
  170. if not FOpened then
  171. begin
  172. // memo pages count start from begining of file!
  173. PageOffsetByHeader := false;
  174. // open physical file
  175. OpenFile;
  176. // read header
  177. HeaderSize := 512;
  178. // determine version
  179. if FDbfVersion = xBaseIII then
  180. PDbtHdr(Header)^.bVer := 3;
  181. VirtualLocks := false;
  182. if FileCreated or (HeaderSize = 0) then
  183. begin
  184. if (FMemoRecordSize = 0) or (FMemoRecordSize > HeaderSize) then
  185. SetNextFreeBlock(1)
  186. else
  187. SetNextFreeBlock(HeaderSize div FMemoRecordSize);
  188. SetBlockLen(FMemoRecordSize);
  189. WriteHeader;
  190. end;
  191. RecordSize := GetBlockLen;
  192. // checking for right blocksize not needed for foxpro?
  193. // todo: why exactly are we testing for 0x7F?
  194. // mod 128 <> 0 <-> and 0x7F <> 0
  195. if (RecordSize = 0) and
  196. ((FDbfVersion in [xFoxPro,xVisualFoxPro]) or ((RecordSize and $7F) <> 0)) then
  197. begin
  198. SetBlockLen(64); //(Visual) FoxPro docs suggest 512 is default; however it is 64: see
  199. //http://technet.microsoft.com/en-us/subscriptions/d6e1ah7y%28v=vs.90%29.aspx
  200. RecordSize := 64;
  201. WriteHeader;
  202. end
  203. else if (RecordSize = 0) then
  204. begin
  205. SetBlockLen(512); //dbase default
  206. RecordSize := 512;
  207. WriteHeader;
  208. end;
  209. // get memory for temporary buffer
  210. GetMem(FBuffer, RecordSize+2);
  211. FBuffer[RecordSize] := #0;
  212. FBuffer[RecordSize+1] := #0;
  213. // now open
  214. FOpened := true;
  215. end;
  216. end;
  217. procedure TMemoFile.Close;
  218. begin
  219. if FOpened then
  220. begin
  221. // close physical file
  222. CloseFile;
  223. // free mem
  224. if FBuffer <> nil then
  225. FreeMemAndNil(Pointer(FBuffer));
  226. // now closed
  227. FOpened := false;
  228. end;
  229. end;
  230. procedure TMemoFile.ReadMemo(BlockNo: Integer; DestStream: TStream);
  231. var
  232. bytesLeft,numBytes,dataStart: Integer;
  233. done: Boolean;
  234. lastc: AnsiChar;
  235. endMemo: PAnsiChar;
  236. begin
  237. // clear dest
  238. DestStream.Position := 0;
  239. DestStream.Size := 0;
  240. // no block to read?
  241. if (BlockNo<=0) or (RecordSize=0) then
  242. exit;
  243. // read first block
  244. numBytes := ReadRecord(BlockNo, @FBuffer[0]);
  245. if numBytes = 0 then
  246. begin
  247. // EOF reached?
  248. exit;
  249. end else
  250. if numBytes < RecordSize then
  251. FillChar(FBuffer[numBytes], RecordSize-numBytes, #0);
  252. bytesLeft := GetMemoSize;
  253. // bytesLeft <> -1 -> memo size is known (FoxPro, dBase4)
  254. // bytesLeft = -1 -> memo size unknown (dBase3)
  255. if bytesLeft <> -1 then
  256. begin
  257. dataStart := 8;
  258. DestStream.Size := bytesLeft;
  259. while bytesLeft > 0 do
  260. begin
  261. // get number of bytes to be read
  262. numBytes := bytesLeft;
  263. // too much for this block?
  264. if numBytes > RecordSize - dataStart then
  265. numBytes := RecordSize - dataStart;
  266. // read block to stream
  267. DestStream.Write(FBuffer[dataStart], numBytes);
  268. // numBytes done
  269. dec(bytesLeft, numBytes);
  270. // still need to read bytes?
  271. if bytesLeft > 0 then
  272. begin
  273. // read next block
  274. inc(BlockNo);
  275. dataStart := 0;
  276. ReadRecord(BlockNo, @FBuffer[0]);
  277. end;
  278. end;
  279. end else begin
  280. // e.g. dbase III memo
  281. done := false;
  282. repeat
  283. // scan for EOF marker/field terminator
  284. endMemo := MemScan(FBuffer, $1A, RecordSize);
  285. // EOF found?
  286. if endMemo <> nil then
  287. begin
  288. // really EOF? expect another 1A or null character
  289. if (endMemo-FBuffer < RecordSize - 1) and
  290. ((endMemo[1] = #$1A) or (endMemo[1] = #0)) then
  291. begin
  292. done := true; //found the end
  293. numBytes := endMemo - FBuffer;
  294. end else begin
  295. // no, fake ending
  296. numBytes := RecordSize;
  297. end;
  298. end else begin
  299. numBytes := RecordSize;
  300. end;
  301. // write to stream
  302. DestStream.Write(FBuffer[0], numBytes);
  303. {
  304. for i := 0 to RecordSize-2 do
  305. begin
  306. if (FBuffer[i]=#$1A) and (FBuffer[i+1]=#$1A) then
  307. begin
  308. if i>0 then
  309. DestStream.Write(FBuffer[0], i);
  310. done := true;
  311. break;
  312. end;
  313. end;
  314. }
  315. if not done then
  316. begin
  317. {
  318. DestStream.Write(FBuffer[0], 512);
  319. }
  320. lastc := FBuffer[RecordSize-1];
  321. inc(BlockNo);
  322. if ReadRecord(BlockNo, @FBuffer[0]) > 0 then
  323. begin
  324. // check if immediate terminator at begin of block
  325. done := (lastc = #$1A) and ((FBuffer[0] = #$1A) or (FBuffer[0] = #0));
  326. // if so, written one character too much
  327. if done then
  328. DestStream.Size := DestStream.Size - 1;
  329. end else begin
  330. // error while reading, stop
  331. done := true;
  332. end;
  333. end;
  334. until done;
  335. end;
  336. end;
  337. procedure TMemoFile.WriteMemo(var BlockNo: Integer; ReadSize: Integer; Src: TStream);
  338. var
  339. bytesBefore: Integer;
  340. bytesAfter: Integer;
  341. totsize: Integer;
  342. readBytes: Integer;
  343. append: Boolean;
  344. tmpRecNo: Integer;
  345. begin
  346. // if no data to write, then don't create new block
  347. if Src.Size = 0 then
  348. begin
  349. BlockNo := 0;
  350. end else begin
  351. if FDbfVersion >= xBaseIV then // dBase4 or FoxPro type
  352. begin
  353. bytesBefore := SizeOf(rBlockHdr);
  354. bytesAfter := 0;
  355. end else begin // dBase3 type, Clipper?
  356. bytesBefore := 0;
  357. bytesAfter := 2;
  358. end;
  359. // if ((bytesBefore + Src.Size + bytesAfter + PDbtHdr(Header).BlockLen-1) div PDbtHdr(Header).BlockLen)
  360. // <= ((ReadSize + PDbtHdr(Header).BlockLen-1) div PDbtHdr(Header).BlockLen) then
  361. // If null memo is used, recordsize may be 0. Test for that.
  362. if (RecordSize=0) or (((bytesBefore + Src.Size + bytesAfter + RecordSize-1) div RecordSize)
  363. <= ((ReadSize + RecordSize-1) div RecordSize)) then
  364. begin
  365. append := false;
  366. end else begin
  367. append := true;
  368. // modifying header -> lock memo header
  369. LockPage(0, true);
  370. BlockNo := GetNextFreeBlock;
  371. if BlockNo = 0 then
  372. begin
  373. SetNextFreeBlock(1);
  374. BlockNo := 1;
  375. end;
  376. end;
  377. tmpRecNo := BlockNo;
  378. Src.Position := 0;
  379. FillChar(FBuffer[0], RecordSize, FEmptySpaceFiller);
  380. if bytesBefore=8 then //Field header
  381. begin
  382. totsize := Src.Size + bytesBefore + bytesAfter;
  383. if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
  384. begin
  385. PBlockHdr(FBuffer)^.MemoType := SwapIntLE($0008FFFF);
  386. PBlockHdr(FBuffer)^.MemoSize := SwapIntLE(totsize);
  387. end else begin
  388. PBlockHdr(FBuffer)^.MemoType := SwapIntLE($01000000);
  389. PBlockHdr(FBuffer)^.MemoSize := SwapIntBE(Src.Size);
  390. end;
  391. end;
  392. repeat
  393. // read bytes, don't overwrite header
  394. readBytes := Src.Read(FBuffer[bytesBefore], RecordSize{PDbtHdr(Header).BlockLen}-bytesBefore);
  395. // end of input data reached? check if we need to write block terminators
  396. while (readBytes < RecordSize - bytesBefore) and (bytesAfter > 0) do
  397. begin
  398. FBuffer[readBytes] := #$1A; //block terminator
  399. Inc(readBytes);
  400. Dec(bytesAfter);
  401. end;
  402. // have we read anything that needs to be written?
  403. if readBytes > 0 then
  404. begin
  405. // clear any unused space
  406. FillChar(FBuffer[bytesBefore+readBytes], RecordSize-readBytes-bytesBefore, FEmptySpaceFiller);
  407. // write to disk
  408. WriteRecord(tmpRecNo, @FBuffer[0]);
  409. Inc(tmpRecNo);
  410. end else break;
  411. // first block read, second block can start at beginning
  412. bytesBefore := 0;
  413. until false;
  414. if append then
  415. begin
  416. SetNextFreeBlock(tmpRecNo);
  417. WriteHeader;
  418. UnlockPage(0);
  419. end;
  420. end;
  421. end;
  422. // ------------------------------------------------------------------
  423. // dBase specific helper routines
  424. // ------------------------------------------------------------------
  425. function TDbaseMemoFile.GetBlockLen: Integer;
  426. begin
  427. // Can you tell me why the header of dbase3 memo contains 1024 and is 512 ?
  428. // answer: BlockLen is not a valid field in memo db3 header
  429. if FDbfVersion = xBaseIII then
  430. Result := 512
  431. else
  432. Result := SwapWordLE(PDbtHdr(Header)^.BlockLen);
  433. end;
  434. function TDbaseMemoFile.GetMemoSize: Integer;
  435. begin
  436. // dBase4 memofiles contain a small 'header'
  437. if (FDbfVersion<>xBaseIII) and (PInteger(@FBuffer[0])^ = Integer(SwapIntLE($0008FFFF))) then
  438. // Subtract size of the block header itself:
  439. Result := SwapIntLE(PBlockHdr(FBuffer)^.MemoSize)-8
  440. else
  441. Result := -1;
  442. end;
  443. function TDbaseMemoFile.GetNextFreeBlock: Integer;
  444. begin
  445. Result := SwapIntLE(PDbtHdr(Header)^.NextBlock);
  446. end;
  447. procedure TDbaseMemoFile.SetNextFreeBlock(BlockNo: Integer);
  448. begin
  449. PDbtHdr(Header)^.NextBlock := SwapIntLE(BlockNo);
  450. end;
  451. procedure TDbaseMemoFile.SetBlockLen(BlockLen: Integer);
  452. begin
  453. // DBase III does not support block sizes<>512 bytes
  454. if (FDbfVersion<>xBaseIII) then
  455. PDbtHdr(Header)^.BlockLen := SwapWordLE(BlockLen);
  456. end;
  457. // ------------------------------------------------------------------
  458. // FoxPro specific helper routines
  459. // ------------------------------------------------------------------
  460. function TFoxProMemoFile.GetBlockLen: Integer;
  461. begin
  462. Result := SwapWordBE(PFptHdr(Header)^.BlockLen);
  463. end;
  464. function TFoxProMemoFile.GetMemoSize: Integer;
  465. begin
  466. Result := SwapIntBE(PBlockHdr(FBuffer)^.MemoSize);
  467. end;
  468. function TFoxProMemoFile.GetNextFreeBlock: Integer;
  469. begin
  470. Result := SwapIntBE(PFptHdr(Header)^.NextBlock);
  471. end;
  472. procedure TFoxProMemoFile.SetNextFreeBlock(BlockNo: Integer);
  473. begin
  474. PFptHdr(Header)^.NextBlock := SwapIntBE(dword(BlockNo));
  475. end;
  476. procedure TFoxProMemoFile.SetBlockLen(BlockLen: Integer);
  477. begin
  478. PFptHdr(Header)^.BlockLen := SwapWordBE(dword(BlockLen));
  479. end;
  480. // ------------------------------------------------------------------
  481. // NULL file (no file) specific helper routines
  482. // ------------------------------------------------------------------
  483. constructor TNullMemoFile.Create(ADbfFile: pointer);
  484. begin
  485. inherited;
  486. end;
  487. procedure TNullMemoFile.OpenFile;
  488. begin
  489. end;
  490. procedure TNullMemoFile.CloseFile;
  491. begin
  492. end;
  493. procedure TNullMemoFile.SetHeaderOffset(NewValue: Integer);
  494. begin
  495. inherited SetHeaderOffset(0);
  496. end;
  497. procedure TNullMemoFile.SetRecordSize(NewValue: Integer);
  498. begin
  499. inherited SetRecordSize(0);
  500. end;
  501. procedure TNullMemoFile.SetHeaderSize(NewValue: Integer);
  502. begin
  503. inherited SetHeaderSize(0);
  504. end;
  505. function TNullMemoFile.LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean;
  506. begin
  507. Result := true;
  508. end;
  509. function TNullMemoFile.UnlockSection(const Offset, Length: Cardinal): Boolean;
  510. begin
  511. Result := true;
  512. end;
  513. function TNullMemoFile.GetBlockLen: Integer;
  514. begin
  515. Result := 0;
  516. end;
  517. function TNullMemoFile.GetMemoSize: Integer;
  518. begin
  519. Result := 0;
  520. end;
  521. function TNullMemoFile.GetNextFreeBlock: Integer;
  522. begin
  523. Result := 0;
  524. end;
  525. procedure TNullMemoFile.SetNextFreeBlock(BlockNo: Integer);
  526. begin
  527. end;
  528. procedure TNullMemoFile.SetBlockLen(BlockLen: Integer);
  529. begin
  530. end;
  531. function TNullMemoFile.ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
  532. begin
  533. Result := 0;
  534. end;
  535. procedure TNullMemoFile.WriteRecord(IntRecNum: Integer; Buffer: Pointer);
  536. begin
  537. end;
  538. end.