16
EPagedFile = Exception;
18
TPagedFileMode = (pfNone, pfMemoryCreate, pfMemoryOpen, pfExclusiveCreate,
19
pfExclusiveOpen, pfReadWriteCreate, pfReadWriteOpen, pfReadOnly);
24
// - exclusive create/open
25
// - read/write create/open
28
// - memory -*-share: N/A -*-locks: disabled -*-indexes: read/write
29
// - exclusive_create -*-share: deny write -*-locks: disabled -*-indexes: read/write
30
// - exclusive_open -*-share: deny write -*-locks: disabled -*-indexes: read/write
31
// - readwrite_create -*-share: deny none -*-locks: enabled -*-indexes: read/write
32
// - readwrite_open -*-share: deny none -*-locks: enabled -*-indexes: read/write
33
// - readonly -*-share: deny none -*-locks: disabled -*-indexes: readonly
35
TPagedFile = class(TObject)
38
FHeaderOffset: Integer;
41
FPageSize: Integer; { need for MDX, where recordsize <> pagesize }
42
FRecordCount: Integer; { actually FPageCount, but we want to keep existing code }
43
FPagesPerRecord: Integer;
45
FCachedRecordCount: Integer;
49
FHeaderModified: Boolean;
50
FPageOffsetByHeader: Boolean; { do pages start after header or just at BOF? }
51
FMode: TPagedFileMode;
52
FTempMode: TPagedFileMode;
53
FUserMode: TPagedFileMode;
56
FVirtualLocks: Boolean;
60
FBufferAhead: Boolean;
62
FBufferOffset: Integer;
64
FBufferReadSize: Integer;
65
FBufferMaxSize: Integer;
66
FBufferModified: Boolean;
69
procedure SetHeaderOffset(NewValue: Integer); virtual;
70
procedure SetRecordSize(NewValue: Integer); virtual;
71
procedure SetHeaderSize(NewValue: Integer); virtual;
72
procedure SetPageSize(NewValue: Integer);
73
procedure SetPageOffsetByHeader(NewValue: Boolean); virtual;
74
procedure SetRecordCount(NewValue: Integer);
75
procedure SetBufferAhead(NewValue: Boolean);
76
procedure SetFileName(NewName: string);
77
procedure SetStream(NewStream: TStream);
78
function LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean; virtual;
79
function UnlockSection(const Offset, Length: Cardinal): Boolean; virtual;
80
procedure UpdateBufferSize;
81
procedure RecalcPagesPerRecord;
83
procedure FlushHeader;
84
procedure FlushBuffer;
85
function ReadChar: Byte;
86
procedure WriteChar(c: Byte);
87
procedure CheckCachedSize(const APosition: Integer);
88
procedure SynchronizeBuffer(IntRecNum: Integer);
89
function Read(Buffer: Pointer; ASize: Integer): Integer;
90
function ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
91
function SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
92
procedure WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
93
procedure SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
94
function GetRecordCount: Integer;
95
procedure UpdateCachedSize(CurrPos: Integer);
97
property VirtualLocks: Boolean read FVirtualLocks write FVirtualLocks;
100
destructor Destroy; override;
102
procedure CloseFile; virtual;
103
procedure OpenFile; virtual;
104
procedure DeleteFile;
105
procedure TryExclusive; virtual;
106
procedure EndExclusive; virtual;
107
procedure CheckExclusiveAccess;
108
procedure DisableForceCreate;
109
function CalcPageOffset(const PageNo: Integer): Integer;
110
function IsRecordPresent(IntRecNum: Integer): boolean;
111
function ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer; virtual;
112
procedure WriteRecord(IntRecNum: Integer; Buffer: Pointer); virtual;
113
procedure WriteHeader; virtual;
114
function FileCreated: Boolean;
115
function IsSharedAccess: Boolean;
116
procedure ResetError;
118
function LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
119
function LockAllPages(const Wait: Boolean): Boolean;
120
procedure UnlockPage(const PageNo: Integer);
121
procedure UnlockAllPages;
123
procedure Flush; virtual;
125
property Active: Boolean read FActive;
126
property AutoCreate: Boolean read FAutoCreate write FAutoCreate; // only write when closed!
127
property Mode: TPagedFileMode read FMode write FMode; // only write when closed!
128
property TempMode: TPagedFileMode read FTempMode;
129
property NeedLocks: Boolean read FNeedLocks;
130
property HeaderOffset: Integer read FHeaderOffset write SetHeaderOffset;
131
property HeaderSize: Integer read FHeaderSize write SetHeaderSize;
132
property RecordSize: Integer read FRecordSize write SetRecordSize;
133
property PageSize: Integer read FPageSize write SetPageSize;
134
property PagesPerRecord: Integer read FPagesPerRecord;
135
property RecordCount: Integer read GetRecordCount write SetRecordCount;
136
property CachedRecordCount: Integer read FCachedRecordCount;
137
property PageOffsetByHeader: Boolean read FPageOffsetbyHeader write SetPageOffsetByHeader;
138
property FileLocked: Boolean read FFileLocked;
139
property Header: PChar read FHeader;
140
property FileName: string read FFileName write SetFileName;
141
property Stream: TStream read FStream write SetStream;
142
property BufferAhead: Boolean read FBufferAhead write SetBufferAhead;
143
property WriteError: Boolean read FWriteError;
159
//====================================================================
161
//====================================================================
162
constructor TPagedFile.Create;
164
FFileName := EmptyStr;
170
FPagesPerRecord := 0;
172
FHeaderModified := false;
173
FPageOffsetByHeader := true;
177
FAutoCreate := false;
178
FVirtualLocks := true;
179
FFileLocked := false;
182
FBufferAhead := false;
183
FBufferModified := false;
187
FWriteError := false;
192
destructor TPagedFile.Destroy;
194
// close physical file
195
if FFileLocked then UnlockAllPages;
197
FFileLocked := false;
200
if FHeader <> nil then
206
procedure TPagedFile.OpenFile;
210
if FActive then exit;
212
// store user specified mode
214
if not (FMode in [pfMemoryCreate, pfMemoryOpen]) then
216
// test if file exists
217
if not FileExists(FFileName) then
219
// if auto-creating, adjust mode
220
if FAutoCreate then case FMode of
221
pfExclusiveOpen: FMode := pfExclusiveCreate;
222
pfReadWriteOpen, pfReadOnly: FMode := pfReadWriteCreate;
224
// it seems the VCL cannot share a file that is created?
225
// create file first, then open it in requested mode
226
// filecreated means 'to be created' in this context ;-)
228
FileClose(FileCreate(FFileName))
230
raise EPagedFile.CreateFmt(STRING_FILE_NOT_FOUND,[FFileName]);
234
pfExclusiveCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
235
pfExclusiveOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
236
pfReadWriteCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
237
pfReadWriteOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
239
fileOpenMode := fmOpenRead or fmShareDenyNone;
242
FStream := TFileStream.Create(FFileName, fileOpenMode);
243
// if creating, then empty file
247
if FStream = nil then
249
FMode := pfMemoryCreate;
250
FStream := TMemoryStream.Create;
254
FCachedSize := Stream.Size;
255
// update whether we need locking
259
FNeedLocks := IsSharedAccess;
262
// allocate memory for bufferahead
266
procedure TPagedFile.CloseFile;
272
// don't free the user's stream
273
if not (FMode in [pfMemoryOpen, pfMemoryCreate]) then
275
// free bufferahead buffer
276
FreeMemAndNil(FBufferPtr);
278
// mode possibly overridden in case of auto-created file
281
FCachedRecordCount := 0;
285
procedure TPagedFile.DeleteFile;
287
// opened -> we can not delete
289
SysUtils.DeleteFile(FileName);
292
function TPagedFile.FileCreated: Boolean;
294
CreationModes: array [pfNone..pfReadOnly] of Boolean =
295
(false, true, false, true, false, true, false, false);
296
// node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
298
Result := CreationModes[FMode];
301
function TPagedFile.IsSharedAccess: Boolean;
303
SharedAccessModes: array [pfNone..pfReadOnly] of Boolean =
304
(false, false, false, false, false, true, true, true);
305
// node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
307
Result := SharedAccessModes[FMode];
310
procedure TPagedFile.CheckExclusiveAccess;
312
// in-memory => exclusive access!
313
if IsSharedAccess then
314
raise EDbfError.Create(STRING_NEED_EXCLUSIVE_ACCESS);
317
function TPagedFile.CalcPageOffset(const PageNo: Integer): Integer;
319
if not FPageOffsetByHeader then
320
Result := FPageSize * PageNo
321
else if PageNo = 0 then
324
Result := FHeaderOffset + FHeaderSize + (FPageSize * (PageNo - 1))
327
procedure TPagedFile.CheckCachedSize(const APosition: Integer);
330
if APosition > FCachedSize then
332
FCachedSize := APosition;
337
function TPagedFile.Read(Buffer: Pointer; ASize: Integer): Integer;
339
// if we cannot read due to a lock, then wait a bit
341
Result := FStream.Read(Buffer^, ASize);
344
// translation to linux???
345
if GetLastError = ERROR_LOCK_VIOLATION then
347
// wait a bit until block becomes available
350
// return empty block
358
procedure TPagedFile.UpdateCachedSize(CurrPos: Integer);
360
// have we added a record?
361
if CurrPos > FCachedSize then
363
// update cached size, always at end
365
Inc(FCachedSize, FRecordSize);
366
Inc(FRecordCount, PagesPerRecord);
367
until FCachedSize >= CurrPos;
371
procedure TPagedFile.FlushBuffer;
373
if FBufferAhead and FBufferModified then
375
WriteBlock(FBufferPtr, FBufferSize, FBufferOffset);
376
FBufferModified := false;
380
function TPagedFile.SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
382
Result := ReadBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
385
procedure TPagedFile.SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
387
WriteBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
390
procedure TPagedFile.SynchronizeBuffer(IntRecNum: Integer);
392
// record outside buffer, flush previous buffer
394
// read new set of records
395
FBufferPage := IntRecNum;
396
FBufferOffset := CalcPageOffset(IntRecNum);
397
if FBufferOffset + FBufferMaxSize > FCachedSize then
398
FBufferReadSize := FCachedSize - FBufferOffset
400
FBufferReadSize := FBufferMaxSize;
401
FBufferSize := FBufferReadSize;
402
FBufferReadSize := ReadBlock(FBufferPtr, FBufferReadSize, FBufferOffset);
405
function TPagedFile.IsRecordPresent(IntRecNum: Integer): boolean;
407
// if in shared mode, recordcount can only increase, check if recordno
408
// in range for cached recordcount
409
if not IsSharedAccess or (IntRecNum > FCachedRecordCount) then
410
FCachedRecordCount := RecordCount;
411
Result := (0 <= IntRecNum) and (IntRecNum <= FCachedRecordCount);
414
function TPagedFile.ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
420
Offset := (IntRecNum - FBufferPage) * PageSize;
421
if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
422
(Offset+RecordSize <= FBufferReadSize) then
424
// have record in buffer, nothing to do here
426
// need to update buffer
427
SynchronizeBuffer(IntRecNum);
428
// check if enough bytes read
429
if RecordSize > FBufferReadSize then
434
// reset offset into buffer
437
// now we have this record in buffer
438
Move(PChar(FBufferPtr)[Offset], Buffer^, RecordSize);
440
Result := RecordSize;
443
Result := SingleReadRecord(IntRecNum, Buffer);
447
procedure TPagedFile.WriteRecord(IntRecNum: Integer; Buffer: Pointer);
453
RecEnd := (IntRecNum - FBufferPage + PagesPerRecord) * PageSize;
454
if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
455
(RecEnd <= FBufferMaxSize) then
458
if RecEnd > FBufferSize then
459
FBufferSize := RecEnd;
461
// record outside buffer, need to synchronize first
462
SynchronizeBuffer(IntRecNum);
463
RecEnd := PagesPerRecord * PageSize;
465
// we can write this record to buffer
466
Move(Buffer^, PChar(FBufferPtr)[RecEnd-RecordSize], RecordSize);
467
FBufferModified := true;
468
// update cached size
469
UpdateCachedSize(FBufferOffset+RecEnd);
472
SingleWriteRecord(IntRecNum, Buffer);
473
// update cached size
474
UpdateCachedSize(FStream.Position);
478
procedure TPagedFile.SetBufferAhead(NewValue: Boolean);
480
if FBufferAhead <> NewValue then
483
FBufferAhead := NewValue;
488
procedure TPagedFile.SetStream(NewStream: TStream);
491
FStream := NewStream;
494
procedure TPagedFile.SetFileName(NewName: string);
497
FFileName := NewName;
500
procedure TPagedFile.UpdateBufferSize;
504
FBufferMaxSize := 65536;
505
if RecordSize <> 0 then
506
Dec(FBufferMaxSize, FBufferMaxSize mod PageSize);
511
if FBufferPtr <> nil then
513
if FBufferAhead and (FBufferMaxSize <> 0) then
514
GetMem(FBufferPtr, FBufferMaxSize)
519
FBufferModified := false;
522
procedure TPagedFile.WriteHeader;
524
FHeaderModified := true;
529
procedure TPagedFile.FlushHeader;
531
if FHeaderModified then
533
FStream.Position := FHeaderOffset;
534
FWriteError := (FStream.Write(FHeader^, FHeaderSize) = 0) or FWriteError;
535
// test if written new header
536
if FStream.Position > FCachedSize then
538
// new header -> record count unknown
539
FCachedSize := FStream.Position;
542
FHeaderModified := false;
546
procedure TPagedFile.ReadHeader;
547
{ assumes header is large enough }
551
// save changes before reading new header
553
// check if header length zero
554
if FHeaderSize <> 0 then
556
// get size left in file for header
557
size := FStream.Size - FHeaderOffset;
558
// header start before EOF?
561
// go to header start
562
FStream.Position := FHeaderOffset;
563
// whole header in file?
564
if size >= FHeaderSize then
566
// read header, nothing to be cleared
567
Read(FHeader, FHeaderSize);
570
// read what we can, clear rest
574
// header start before EOF, clear header
577
FillChar(FHeader[size], FHeaderSize-size, 0);
581
procedure TPagedFile.TryExclusive;
582
const NewTempMode: array[pfReadWriteCreate..pfReadOnly] of TPagedFileMode =
583
(pfReadWriteOpen, pfReadWriteOpen, pfReadOnly);
585
// already in temporary exclusive mode?
586
if (FTempMode = pfNone) and IsSharedAccess then
588
// save temporary mode, if now creating, then reopen non-create
589
FTempMode := NewTempMode[FMode];
590
// try exclusive mode
592
FMode := pfExclusiveOpen;
598
// we failed, reopen normally
605
procedure TPagedFile.EndExclusive;
607
// are we in temporary file mode?
608
if FTempMode <> pfNone then
617
procedure TPagedFile.DisableForceCreate;
620
pfExclusiveCreate: FMode := pfExclusiveOpen;
621
pfReadWriteCreate: FMode := pfReadWriteOpen;
625
procedure TPagedFile.SetHeaderOffset(NewValue: Integer);
627
// *) assumes is called right before SetHeaderSize
630
if FHeaderOffset <> NewValue then
633
FHeaderOffset := NewValue;
637
procedure TPagedFile.SetHeaderSize(NewValue: Integer);
639
if FHeaderSize <> NewValue then
642
if (FHeader <> nil) and (NewValue <> 0) then
644
FHeaderSize := NewValue;
645
if FHeaderSize <> 0 then
646
GetMem(FHeader, FHeaderSize);
652
procedure TPagedFile.SetRecordSize(NewValue: Integer);
654
if FRecordSize <> NewValue then
656
FRecordSize := NewValue;
657
FPageSize := NewValue;
659
RecalcPagesPerRecord;
663
procedure TPagedFile.SetPageSize(NewValue: Integer);
665
if FPageSize <> NewValue then
667
FPageSize := NewValue;
669
RecalcPagesPerRecord;
674
procedure TPagedFile.RecalcPagesPerRecord;
676
if FPageSize = 0 then
679
FPagesPerRecord := FRecordSize div FPageSize;
682
function TPagedFile.GetRecordCount: Integer;
686
// file size changed?
689
currSize := FStream.Size;
690
if currSize <> FCachedSize then
692
FCachedSize := currSize;
697
// try to optimize speed
700
// no file? test flags
701
if (FPageSize = 0) or not FActive then
704
if FPageOffsetByHeader then
705
FRecordCount := (FCachedSize - FHeaderSize - FHeaderOffset) div FPageSize
707
FRecordCount := FCachedSize div FPageSize;
708
if FRecordCount < 0 then
712
FNeedRecalc := false;
714
Result := FRecordCount;
717
procedure TPagedFile.SetRecordCount(NewValue: Integer);
719
if RecordCount <> NewValue then
721
if FPageOffsetByHeader then
722
FCachedSize := FHeaderSize + FHeaderOffset + FPageSize * NewValue
724
FCachedSize := FPageSize * NewValue;
725
// FCachedSize := CalcPageOffset(NewValue);
726
FRecordCount := NewValue;
727
FStream.Size := FCachedSize;
731
procedure TPagedFile.SetPageOffsetByHeader(NewValue: Boolean);
733
if FPageOffsetByHeader <> NewValue then
735
FPageOffsetByHeader := NewValue;
740
procedure TPagedFile.WriteChar(c: Byte);
742
FWriteError := (FStream.Write(c, 1) = 0) or FWriteError;
745
function TPagedFile.ReadChar: Byte;
750
procedure TPagedFile.Flush;
754
function TPagedFile.ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
756
FStream.Position := APosition;
757
CheckCachedSize(APosition);
758
Result := Read(BlockPtr, ASize);
761
procedure TPagedFile.WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
762
// assumes a lock is held if necessary prior to calling this function
764
FStream.Position := APosition;
765
CheckCachedSize(APosition);
766
FWriteError := (FStream.Write(BlockPtr^, ASize) = 0) or FWriteError;
769
procedure TPagedFile.ResetError;
771
FWriteError := false;
774
// BDE compatible lock offset found!
777
LockOffset = $EFFFFFFE; // BDE compatible
780
LockOffset = $7FFFFFFF;
784
// dBase supports maximum of a billion records
785
LockStart = LockOffset - 1000000000;
787
function TPagedFile.LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean;
788
// assumes FNeedLock = true
792
// FNeedLocks => FStream is of type TFileStream
795
Result := LockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
796
// test if lock violation, then wait a bit and try again
797
if not Result and Wait then
799
if (GetLastError = ERROR_LOCK_VIOLATION) then
804
until Result or not Wait or Failed;
807
function TPagedFile.UnlockSection(const Offset, Length: Cardinal): Boolean;
809
Result := UnlockFile(TFileStream(FStream).Handle, Offset, 0, Length, 0);
812
function TPagedFile.LockAllPages(const Wait: Boolean): Boolean;
817
// do we need locking?
818
if FNeedLocks and not FFileLocked then
820
if FVirtualLocks then
822
{$ifdef SUPPORT_UINT32_CARDINAL}
824
Length := LockOffset - LockStart + FileLockSize;
826
// delphi 3 has strange types:
827
// cardinal 0..2 GIG ?? does it produce correct code?
828
Offset := Cardinal(LockStart);
829
Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
835
// lock requested section
836
Result := LockSection(Offset, Length, Wait);
837
FFileLocked := Result;
842
procedure TPagedFile.UnlockAllPages;
847
// do we need locking?
848
if FNeedLocks and FFileLocked then
850
if FVirtualLocks then
852
{$ifdef SUPPORT_UINT32_CARDINAL}
854
Length := LockOffset - LockStart + FileLockSize;
856
// delphi 3 has strange types:
857
// cardinal 0..2 GIG ?? does it produce correct code?
858
Offset := Cardinal(LockStart);
859
Length := Cardinal(LockOffset) - Cardinal(LockStart) + FileLockSize;
865
// unlock requested section
866
// FNeedLocks => FStream is of type TFileStream
867
FFileLocked := not UnlockSection(Offset, Length);
871
function TPagedFile.LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
876
// do we need locking?
877
if FNeedLocks and not FFileLocked then
879
if FVirtualLocks then
881
Offset := LockOffset - Cardinal(PageNo);
884
Offset := CalcPageOffset(PageNo);
885
Length := RecordSize;
887
// lock requested section
888
Result := LockSection(Offset, Length, Wait);
893
procedure TPagedFile.UnlockPage(const PageNo: Integer);
898
// do we need locking?
899
if FNeedLocks and not FFileLocked then
901
// calc offset + length
902
if FVirtualLocks then
904
Offset := LockOffset - Cardinal(PageNo);
907
Offset := CalcPageOffset(PageNo);
908
Length := RecordSize;
910
// unlock requested section
911
// FNeedLocks => FStream is of type TFileStream
912
UnlockSection(Offset, Length);