📄 dbf_pgfile.pas
字号:
unit dbf_pgfile;
interface
{$I dbf_common.inc}
uses
Classes,
SysUtils,
dbf_common;
//const
// MaxHeaders = 256;
type
EPagedFile = Exception;
TPagedFileMode = (pfNone, pfMemoryCreate, pfMemoryOpen, pfExclusiveCreate,
pfExclusiveOpen, pfReadWriteCreate, pfReadWriteOpen, pfReadOnly);
// access levels:
//
// - memory create
// - exclusive create/open
// - read/write create/open
// - readonly open
//
// - memory -*-share: N/A -*-locks: disabled -*-indexes: read/write
// - exclusive_create -*-share: deny write -*-locks: disabled -*-indexes: read/write
// - exclusive_open -*-share: deny write -*-locks: disabled -*-indexes: read/write
// - readwrite_create -*-share: deny none -*-locks: enabled -*-indexes: read/write
// - readwrite_open -*-share: deny none -*-locks: enabled -*-indexes: read/write
// - readonly -*-share: deny none -*-locks: disabled -*-indexes: readonly
TPagedFile = class(TObject)
protected
FStream: TStream;
FHeaderOffset: Integer;
FHeaderSize: Integer;
FRecordSize: Integer;
FPageSize: Integer; { need for MDX, where recordsize <> pagesize }
FRecordCount: Integer; { actually FPageCount, but we want to keep existing code }
FPagesPerRecord: Integer;
FCachedSize: Integer;
FCachedRecordCount: Integer;
FHeader: PChar;
FActive: Boolean;
FNeedRecalc: Boolean;
FHeaderModified: Boolean;
FPageOffsetByHeader: Boolean; { do pages start after header or just at BOF? }
FMode: TPagedFileMode;
FTempMode: TPagedFileMode;
FUserMode: TPagedFileMode;
FAutoCreate: Boolean;
FNeedLocks: Boolean;
FVirtualLocks: Boolean;
FFileLocked: Boolean;
FFileName: string;
FBufferPtr: Pointer;
FBufferAhead: Boolean;
FBufferPage: Integer;
FBufferOffset: Integer;
FBufferSize: Integer;
FBufferReadSize: Integer;
FBufferMaxSize: Integer;
FBufferModified: Boolean;
FWriteError: Boolean;
protected
procedure SetHeaderOffset(NewValue: Integer); virtual;
procedure SetRecordSize(NewValue: Integer); virtual;
procedure SetHeaderSize(NewValue: Integer); virtual;
procedure SetPageSize(NewValue: Integer);
procedure SetPageOffsetByHeader(NewValue: Boolean); virtual;
procedure SetRecordCount(NewValue: Integer);
procedure SetBufferAhead(NewValue: Boolean);
procedure SetFileName(NewName: string);
procedure SetStream(NewStream: TStream);
function LockSection(const Offset, Length: Cardinal; const Wait: Boolean): Boolean; virtual;
function UnlockSection(const Offset, Length: Cardinal): Boolean; virtual;
procedure UpdateBufferSize;
procedure RecalcPagesPerRecord;
procedure ReadHeader;
procedure FlushHeader;
procedure FlushBuffer;
function ReadChar: Byte;
procedure WriteChar(c: Byte);
procedure CheckCachedSize(const APosition: Integer);
procedure SynchronizeBuffer(IntRecNum: Integer);
function Read(Buffer: Pointer; ASize: Integer): Integer;
function ReadBlock(const BlockPtr: Pointer; const ASize, APosition: Integer): Integer;
function SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
procedure WriteBlock(const BlockPtr: Pointer; const ASize, APosition: Integer);
procedure SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
function GetRecordCount: Integer;
procedure UpdateCachedSize(CurrPos: Integer);
property VirtualLocks: Boolean read FVirtualLocks write FVirtualLocks;
public
constructor Create;
destructor Destroy; override;
procedure CloseFile; virtual;
procedure OpenFile; virtual;
procedure DeleteFile;
procedure TryExclusive; virtual;
procedure EndExclusive; virtual;
procedure CheckExclusiveAccess;
procedure DisableForceCreate;
function CalcPageOffset(const PageNo: Integer): Integer;
function IsRecordPresent(IntRecNum: Integer): boolean;
function ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer; virtual;
procedure WriteRecord(IntRecNum: Integer; Buffer: Pointer); virtual;
procedure WriteHeader; virtual;
function FileCreated: Boolean;
function IsSharedAccess: Boolean;
procedure ResetError;
function LockPage(const PageNo: Integer; const Wait: Boolean): Boolean;
function LockAllPages(const Wait: Boolean): Boolean;
procedure UnlockPage(const PageNo: Integer);
procedure UnlockAllPages;
procedure Flush; virtual;
property Active: Boolean read FActive;
property AutoCreate: Boolean read FAutoCreate write FAutoCreate; // only write when closed!
property Mode: TPagedFileMode read FMode write FMode; // only write when closed!
property TempMode: TPagedFileMode read FTempMode;
property NeedLocks: Boolean read FNeedLocks;
property HeaderOffset: Integer read FHeaderOffset write SetHeaderOffset;
property HeaderSize: Integer read FHeaderSize write SetHeaderSize;
property RecordSize: Integer read FRecordSize write SetRecordSize;
property PageSize: Integer read FPageSize write SetPageSize;
property PagesPerRecord: Integer read FPagesPerRecord;
property RecordCount: Integer read GetRecordCount write SetRecordCount;
property CachedRecordCount: Integer read FCachedRecordCount;
property PageOffsetByHeader: Boolean read FPageOffsetbyHeader write SetPageOffsetByHeader;
property FileLocked: Boolean read FFileLocked;
property Header: PChar read FHeader;
property FileName: string read FFileName write SetFileName;
property Stream: TStream read FStream write SetStream;
property BufferAhead: Boolean read FBufferAhead write SetBufferAhead;
property WriteError: Boolean read FWriteError;
end;
implementation
uses
{$ifdef WINDOWS}
Windows,
{$else}
{$ifdef KYLIX}
Libc,
{$endif}
Types, dbf_wtil,
{$endif}
dbf_str;
//====================================================================
// TPagedFile
//====================================================================
constructor TPagedFile.Create;
begin
FFileName := EmptyStr;
FHeaderOffset := 0;
FHeaderSize := 0;
FRecordSize := 0;
FRecordCount := 0;
FPageSize := 0;
FPagesPerRecord := 0;
FActive := false;
FHeaderModified := false;
FPageOffsetByHeader := true;
FNeedLocks := false;
FMode := pfReadOnly;
FTempMode := pfNone;
FAutoCreate := false;
FVirtualLocks := true;
FFileLocked := false;
FHeader := nil;
FBufferPtr := nil;
FBufferAhead := false;
FBufferModified := false;
FBufferSize := 0;
FBufferMaxSize := 0;
FBufferOffset := 0;
FWriteError := false;
inherited;
end;
destructor TPagedFile.Destroy;
begin
// close physical file
if FFileLocked then UnlockAllPages;
CloseFile;
FFileLocked := false;
// free mem
if FHeader <> nil then
FreeMem(FHeader);
inherited;
end;
procedure TPagedFile.OpenFile;
var
fileOpenMode: Word;
begin
if FActive then exit;
// store user specified mode
FUserMode := FMode;
if not (FMode in [pfMemoryCreate, pfMemoryOpen]) then
begin
// test if file exists
if not FileExists(FFileName) then
begin
// if auto-creating, adjust mode
if FAutoCreate then case FMode of
pfExclusiveOpen: FMode := pfExclusiveCreate;
pfReadWriteOpen, pfReadOnly: FMode := pfReadWriteCreate;
end;
// it seems the VCL cannot share a file that is created?
// create file first, then open it in requested mode
// filecreated means 'to be created' in this context ;-)
if FileCreated then
FileClose(FileCreate(FFileName))
else
raise EPagedFile.CreateFmt(STRING_FILE_NOT_FOUND,[FFileName]);
end;
// specify open mode
case FMode of
pfExclusiveCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
pfExclusiveOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyWrite;
pfReadWriteCreate: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
pfReadWriteOpen: fileOpenMode := fmOpenReadWrite or fmShareDenyNone;
else // => readonly
fileOpenMode := fmOpenRead or fmShareDenyNone;
end;
// open file
FStream := TFileStream.Create(FFileName, fileOpenMode);
// if creating, then empty file
if FileCreated then
FStream.Size := 0;
end else begin
if FStream = nil then
begin
FMode := pfMemoryCreate;
FStream := TMemoryStream.Create;
end;
end;
// init size var
FCachedSize := Stream.Size;
// update whether we need locking
{$ifdef _DEBUG}
FNeedLocks := true;
{$else}
FNeedLocks := IsSharedAccess;
{$endif}
FActive := true;
// allocate memory for bufferahead
UpdateBufferSize;
end;
procedure TPagedFile.CloseFile;
begin
if FActive then
begin
FlushHeader;
FlushBuffer;
// don't free the user's stream
if not (FMode in [pfMemoryOpen, pfMemoryCreate]) then
FreeAndNil(FStream);
// free bufferahead buffer
FreeMemAndNil(FBufferPtr);
// mode possibly overridden in case of auto-created file
FMode := FUserMode;
FActive := false;
FCachedRecordCount := 0;
end;
end;
procedure TPagedFile.DeleteFile;
begin
// opened -> we can not delete
if not FActive then
SysUtils.DeleteFile(FileName);
end;
function TPagedFile.FileCreated: Boolean;
const
CreationModes: array [pfNone..pfReadOnly] of Boolean =
(false, true, false, true, false, true, false, false);
// node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
begin
Result := CreationModes[FMode];
end;
function TPagedFile.IsSharedAccess: Boolean;
const
SharedAccessModes: array [pfNone..pfReadOnly] of Boolean =
(false, false, false, false, false, true, true, true);
// node, memcr, memop, excr, exopn, rwcr, rwopn, rdonly
begin
Result := SharedAccessModes[FMode];
end;
procedure TPagedFile.CheckExclusiveAccess;
begin
// in-memory => exclusive access!
if IsSharedAccess then
raise EDbfError.Create(STRING_NEED_EXCLUSIVE_ACCESS);
end;
function TPagedFile.CalcPageOffset(const PageNo: Integer): Integer;
begin
if not FPageOffsetByHeader then
Result := FPageSize * PageNo
else if PageNo = 0 then
Result := 0
else
Result := FHeaderOffset + FHeaderSize + (FPageSize * (PageNo - 1))
end;
procedure TPagedFile.CheckCachedSize(const APosition: Integer);
begin
// file expanded?
if APosition > FCachedSize then
begin
FCachedSize := APosition;
FNeedRecalc := true;
end;
end;
function TPagedFile.Read(Buffer: Pointer; ASize: Integer): Integer;
begin
// if we cannot read due to a lock, then wait a bit
repeat
Result := FStream.Read(Buffer^, ASize);
if Result = 0 then
begin
// translation to linux???
if GetLastError = ERROR_LOCK_VIOLATION then
begin
// wait a bit until block becomes available
Sleep(1);
end else begin
// return empty block
exit;
end;
end else
exit;
until false;
end;
procedure TPagedFile.UpdateCachedSize(CurrPos: Integer);
begin
// have we added a record?
if CurrPos > FCachedSize then
begin
// update cached size, always at end
repeat
Inc(FCachedSize, FRecordSize);
Inc(FRecordCount, PagesPerRecord);
until FCachedSize >= CurrPos;
end;
end;
procedure TPagedFile.FlushBuffer;
begin
if FBufferAhead and FBufferModified then
begin
WriteBlock(FBufferPtr, FBufferSize, FBufferOffset);
FBufferModified := false;
end;
end;
function TPagedFile.SingleReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
begin
Result := ReadBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
end;
procedure TPagedFile.SingleWriteRecord(IntRecNum: Integer; Buffer: Pointer);
begin
WriteBlock(Buffer, RecordSize, CalcPageOffset(IntRecNum));
end;
procedure TPagedFile.SynchronizeBuffer(IntRecNum: Integer);
begin
// record outside buffer, flush previous buffer
FlushBuffer;
// read new set of records
FBufferPage := IntRecNum;
FBufferOffset := CalcPageOffset(IntRecNum);
if FBufferOffset + FBufferMaxSize > FCachedSize then
FBufferReadSize := FCachedSize - FBufferOffset
else
FBufferReadSize := FBufferMaxSize;
FBufferSize := FBufferReadSize;
FBufferReadSize := ReadBlock(FBufferPtr, FBufferReadSize, FBufferOffset);
end;
function TPagedFile.IsRecordPresent(IntRecNum: Integer): boolean;
begin
// if in shared mode, recordcount can only increase, check if recordno
// in range for cached recordcount
if not IsSharedAccess or (IntRecNum > FCachedRecordCount) then
FCachedRecordCount := RecordCount;
Result := (0 <= IntRecNum) and (IntRecNum <= FCachedRecordCount);
end;
function TPagedFile.ReadRecord(IntRecNum: Integer; Buffer: Pointer): Integer;
var
Offset: Integer;
begin
if FBufferAhead then
begin
Offset := (IntRecNum - FBufferPage) * PageSize;
if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
(Offset+RecordSize <= FBufferReadSize) then
begin
// have record in buffer, nothing to do here
end else begin
// need to update buffer
SynchronizeBuffer(IntRecNum);
// check if enough bytes read
if RecordSize > FBufferReadSize then
begin
Result := 0;
exit;
end;
// reset offset into buffer
Offset := 0;
end;
// now we have this record in buffer
Move(PChar(FBufferPtr)[Offset], Buffer^, RecordSize);
// successful
Result := RecordSize;
end else begin
// no buffering
Result := SingleReadRecord(IntRecNum, Buffer);
end;
end;
procedure TPagedFile.WriteRecord(IntRecNum: Integer; Buffer: Pointer);
var
RecEnd: Integer;
begin
if FBufferAhead then
begin
RecEnd := (IntRecNum - FBufferPage + PagesPerRecord) * PageSize;
if (FBufferPage <> -1) and (FBufferPage <= IntRecNum) and
(RecEnd <= FBufferMaxSize) then
begin
// extend buffer?
if RecEnd > FBufferSize then
FBufferSize := RecEnd;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -